5 ms·
I watched the video segment. I really appreciated the presentation style and visuals—the explanation of your procedure is very clear. But I'm having trouble u
by zestyping 7y ago
I watched the video segment. I really appreciated the presentation style and visuals—the explanation of your procedure is very clear.
But I'm having trouble understanding how this works in the real world.
In your example, the thing that took 62 seconds to build and test four times is "invoke an empty constructor in another file". That is the sort of thing that I think of as a single task, perhaps taking 10 to 15 seconds. Dividing it into four tiny tasks would only generate work for me; testing it four times would provide no benefit because the task is so simple. The example feels to me like a toy example.
I'm having difficulty seeing how to extend this technique to non-trivial tasks. The moment I do "real work" (e.g. match a string against a regular expression), writing a series of tests that verifies enough cases to establish correctness does not take 10 seconds; it can take 2 or 5 or 20 minutes.
And that's where the author's complaint starts to make sense. It may be that when I write the code, the requirements I have in mind are underspecified or incorrect (e.g. I don't yet know whether I need whitespace to be significant because I haven't designed the rest of the program yet, so I plan to write the regular expression without allowing extra whitespace).
This is where I get stuck. In situations like this:
(a) If I write tests that verify only the requirements that I am absolutely certain will not change, then I risk ending up with a program that has lots of incomplete tests and bugs going undetected.
(b) If I write a test that completely verifies the behaviour of the code I'm about to write, then I risk ending up with tests that overconstrain or incorrectly constrain the code, so I get the problem the author described: as I'm building the rest of the program, I realize that I need to make adjustments (e.g. it becomes clear that I should ignore extra whitespace), which means I now need to go back and change the test as well as my code, and repeat.
It's not possible for the requirements to always be 100% complete and perfectly correct in my mind in advance. The type of situation the author is describing happens all the time because the process of constructing the program is a significant part of how the requirements become clear. This is what the author is getting at, I think.
Have I deeply misunderstood TDD?
- jdlshore 7y agoIt is a trivial example, no question. It's part of a bigger tutorial example you can find here: https://www.letscodejavascript.com/v3/comments/tdd_intro/5 https://www.letscodejavascript.com/v3/comments/tdd_intro/5 There's a different, written example here (scroll down to "A TDD Example"): https://www.jamesshore.com/Agile-Book/test_driven_development.html https://www.jamesshore.com/Agile-Book/test_driven_developmen... Those examples are for beginners, and not totally representative of the real world. For real-world TDD, check out my "Let's Play TDD" or "Let's Code JavaScript" screencasts, which are listed in my profile. > Have I deeply misunderstood TDD? Maybe? A defining feature of TDD is that you iterate, so you wouldn't take 2-20 minutes to write a series of tests. Instead, you'd write one test, get that to work, modify it or write the next test, get that to work, etc. Part of the skill of doing TDD well is figuring out which tests to write first, so that this iterative cycle forms a smooth path from beginning to end, while still allowing you to discover new things about your requirements and design as you go. Another part of the skill is testing the behavior of your code, not its implementation, so that you don't overconstrain the implementation. Implementation changes that don't affect behavior shouldn't require test changes. This is hard and many people struggle with this. TDD is easy to learn but hard to master. I personally find it very worthwhile. The confidence it gives me in my code is very freeing, and I like not having to spend much time debugging. TDD isn't perfect, nothing is, but the problems the OP described don't match my experience.
- zestyping 7y agoThe place where I'm getting stuck is the claim that all programming can be done in steps that small. That just doesn't seem realistic. For instance, how could one possibly write a complete test for the behaviour of a regular expression that matches a C comment in less than 2 minutes or even 5 minutes? The test has to be understandable to other readers, so I would easily spend a few minutes just documenting it carefully so that other readers could convince themselves that the test is complete.
- jdlshore 7y agoYou build it up gradually. For example, let's say we're writing an isComment function (and please excuse any misunderstandings about C comment syntax): Test: it("starts and ends with slash-star", function() { assert.isTrue(isComment("/**/"), "empty comment"); assert.isFalse(isComment(""), "empty string"); assert.isFalse(isComment("foo"), "not a comment"); }); Code: isComment(text) { return text.matches(/\/\*\*\//); // excuse any errors; haven't actually tried this } That's the first TDD loop. Takes less than a minute. But it's obviously incomplete, so now we build it up, step by step. Add another test: it("can contain text", function() { assert.isTrue(isComment("/* foo */")); } Modify the code: isComment(text) { return text.matches(/\/\*.*?\*\//); } That also takes less than a minute. We continue in this way, step by step, handling more and more specific edge cases, until we think we've handled all the cases. For example, the next test might be it("can span multiple lines").
- zestyping 7y agoHmm. Okay, so my incorrect assumption was that the test has to be correct. In this process, it is okay for the test to be incomplete. I've been pondering why this feels so counterintuitive to me. I think it's because, in traditional testing, the test is treated as authoritative. Your test is supposed to be 100% correct, and then you make the code good enough to pass the test. The word "test" suggests a teacher grading an exam: the teacher must be 100% correct. So, I wonder if it is helpful when explaining this approach to explicitly set aside this assumption. The program and the test can both be incomplete. Would it be an overstatement to say it seems like the test and the program have more of a symmetric relationship, rather than an asymmetric one? Thanks for taking the time to explain this!