7 ms·
Back in 2005, Microsoft published an article about TDD that was wrong. Not just a little bit wrong, completely and utterly wrong. I wrote about it at the time:
by jdlshore 7y ago
Back in 2005, Microsoft published an article about TDD that was wrong. Not just a little bit wrong, completely and utterly wrong. I wrote about it at the time:
https://www.jamesshore.com/Blog/Microsoft-Gets-TDD-Completely-Wrong.html https://www.jamesshore.com/Blog/Microsoft-Gets-TDD-Completel...
The authors of that article described TDD the same way the OP's polemic does: 1) write your tests 2) implement the tests.
But that's not how TDD works.
Every complaint the author has stems from this misunderstanding.
If you're interested in how TDD and related practices actually work, my talk from last month's Pacific Northwest Software Quality Conference has been getting a lot of praise on Twitter. The whole thing's worth watching, but the TDD-specific part starts at 15:21.
Whole video: https://www.youtube.com/watch?v=_Dv4M39Arec https://www.youtube.com/watch?v=_Dv4M39Arec
TDD part: https://youtu.be/_Dv4M39Arec?t=921 https://youtu.be/_Dv4M39Arec?t=921
- zestyping 7y agoI 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").