Confused about TDD Article in DNDJ
Scott Dockendorf has an article in the December 2004 issue of .NET Developer's Journal on Unit Testing in Whidbey. It's great to see so many Microsoft-oriented magazines taking up the call to unit testing. Though it probably wouldn’t be happening if Microsoft weren't on the verge of releasing unit testing tools. Nonetheless, and for what ever reason, it's a great relief to see unit testing getting so much attention. If we could just get .NET developers to download NUnit and begin to learn how to unit test between now and the release of the next version of Visual Studio we'd really be cookin'.
Scott does a good job of showing how to use VS 2005's unit testing tools, but I found that he sometimes referred to some of the practices outlined in the article as Test-Driven Development in a kind of ambiguous way. In some cases, I found the TDD moniker used in context in a way that was misleading and adding to TDD misconceptions. I fear that I might just be reading the article from the wrong perspective and not seeing some of the points being made, but I fear even more that others might misread the article as well, and make their first forays into TDD on shaky conceptual footing.
So, at the risk of looking like I'm going on the attack of a developer community fellow and TDD brother, I'm going to take an unsolicited stab at making some clarifications about the article as it applies to TDD for myself and potentially for those folks who might be left feeling a bit quizzical.
On Page 44, in the first column, near the bottom of the page, Scott outlines the TDD approach in a paragraph entitled: "What is TDD's Code-First Approach".
1. Thoroughly design your software. Know exactly what the software must accomplish before writing a single test.
2. Use this design to create the interfaces and public methods for your objects (no implementation code).
3. Use design requirements to build and execute tests, and watch them fail (Red).
4. Write our implementation code and rerun our tests to watch them pass (Green).
5. Interrogate the results and performance, tweak the code as necessary for structure, performance, etc. (Refactor).
Point number one gave me pause. It may just be the way Scott chose to word this point, but it can be taken to mean something that couldn’t be more at odds with TDD and XP. TDD process helps achieve good design (good and simple is the goal), but it doesn’t necessarilly start with good design, and certainly not often with thorough design.
I could read this first point and conclude that there's nothing in TDD that isn’t in plain old OOA&D today - design is good, design is done up front. Thoroughly designing software and knowing what the software must accomplish before coding is what XP and TDD posits as an unlikelihood, and largely dismisses it in favor of an organic and evolutionary approach to design. If by thorough design the author means detailed design or near-complete design, then the first point isn’t in keeping with TDD values. Detailed design in anything but code is supplanted in favor of micro-iterative process and design during TDD-styled programming. TDD is design.
That said, having solid requirements and writing low-level stories is good and desired, so I'm confused. I don’t know if Scott is saying do detailed design up front, or have good requirements up front. In either case, TDD is geared for situations where requirements can be in flux, so I'm not sure how much traction either of those statements has with TDD. Having solid requirements is not always achievable, and this is one of the main arguments for organic design, evolutionary approaches, and Test-Driven Development.
In point number two, Scott reinforces my quandary regarding up front design by seemingly recommending architecture-centric process for code design over test-driven process. Thoroughly defining a public API up front isn’t really TDD. That's not to say that visual (or other) modeling doesn’t exist in an XP or test-driven project. Visual modeling with UML, for example, is typically used at a high-level, maybe not as high as CRC and story cards, but typically not as low as having a thorough picture of what a class's public API will be. Defining a class's public API is what TDD is for, and it's fed from stories, test lists, and todo lists. A class's public API wouldn’t be thoroughly modeled - even mentally - before writing the first test unless perhaps it was a simple, minimal class.
In point three, Scott confuses me with the use of the plural "tests" in "Use design requirements to build and execute tests", and the use of "them" in "and watch them fail". I found my self confounded by this a couple of times in the article where it seemed that Scott is suggesting that many newly-written failing tests may exists all at once - or at least that's how I read it. It would be considered a really bad idea to write many new failing tests all at once. In fact, it would be difficult to accomplish. TDD is micro-iterative. The first time you see TDD, you will likely be astonished at how small the steps are.
If I were coding a new method called Withdraw() and a class called Account, I would start with a failing test in the AccountFixture test class. The test method would likely also be called Withdraw(). I might just invoke Assert.Fail() from within AccountFixture.Withdraw() to get the failure and this calibrate against false positives, or I might just skip ahead to a failing assertion that reflected the real business logic.
At the point in time where AccountFixture.Withdraw() has a failing test, I wouldn’t also have failing tests in AccountFixture.Deposit(), and AccountFixture.Transfer(). The reason for this is that the Deposit() and Transfer() methods in the fixture class would more than likely be calling the Deposit() and Transfer() methods in the Account class. Since at this point in a TDD micro-increment the Account.Withdraw() method isn’t complete, the Deposit() and Transfer() methods wouldn't even exist. If the target methods didn’t exist, but their test methods did, then this situation would be considered bad TDD form. You wouldn’t begin to code other test methods and business methods until the work on Account.Withdraw() and AccountFixture.Withdraw() was done.
You might find yourself in the situation with many new failing tests and new stub methods if you designed your class upfront and possibly generated it with XDE, Together, or some other UML-to-code (or other) tool. That's ok, but it's architecture-centric development, not (really) test-driven development. Even if you do find yourself with a skeleton class library generated from a tool, you could still do TDD, but you wouldn’t implement failing tests for each public member stub in the class, you would still work one public member at a time from the tests until you were done.
The only other time that you might find yourself in a situation with many failing methods in the AccountFixture class is when you've just made some change to the Account class that broke the class and made many or all of its tests fail.
In point number four, Scott seems to change the language up a bit, which makes me think that maybe I misread the other points. He says, "Write our implementation code and rerun our tests to watch them pass". This doesn't seem to suggest that this isn’t done iteratively, but it doesn’t really reinforce the iterative and micro-iterative approach that characterizes TDD either. Dunno. Like I said, I'm having a bit of a hard time parsing the specifics and I'm definitely confused.
This next point is really picky. It really has nothing to do with the topic of communicating TDD, but it does talk to the underlying motivation for this blog post. In point number five, Scott seems to suggest that code tweaks and refactoring are one and the same. I not sure that I would personally wholeheartedly agree with the equivocation of refactoring and tweaking for performance or even structure. Although Scott references Fowler's refactoring catalog, so I guess he's just kinda using some jargon when making this point. And in truth, I guess it's the jargonizing of agile approaches that I'm trying to shed some light on. In this particular case, I would want to stand up (and I guess I am) and say that tweaking and refactoring are not equivalent.
The refactoring book is 400 pages of often rigorous engineering process aimed at improving code design. I don’t really think it's a healthy thing to point to every code change a programmer can make and call it refactoring. Over the past year or so the word refactoring has begun to be used in a number of somewhat incongruous contexts. I'm not saying that language shouldn't evolve and that meaning shouldn't broaden to encompass new understanding, but it shouldn’t necessarily devolve either, especially when it leads to ambiguation and makes communication less clear.
On page 45, in a section called "Feature #2: Unit Testing/Visual Studio 2005 Integration", Scott makes a comment about the TDD process that seems to be a comment about unit testing in general rather than TDD. At this point in the article, Scott has just walked the reader through some impressive test code generation in VS 2005. If you haven’t had much time to explore the power of the testing tools coming in Visual Studio this year, this section of Scott's article does a good job of demonstrating the extent of what can be done for creating tests from an existing class.
Scott seems to imply that the test code generation step of his walkthrough is part of the TDD process. Again, I'm not sure if I'm reading this right or not because the comment is really made about the next step in the process. Nonetheless, and just for the sake of clarity, generating test code from an existing class isn’t TDD. In fact, it can’t be TDD. Again, in TDD, the target class wouldn’t exist before the fixture class, the target methods wouldn’t exist before the test methods, and the methods come into existence sequentially through the TDD process rather than all at once.
In reading and re-reading the article, I'm left feeling quite unsure whether I know where Scott is coming from, and I find it hard to distinguish when he's talking about plain old unit testing and Test-Driven Development.
It's a good overview of VS 2005's support for unit testing, and it harkens the coming focus that will be given to unit testing in the next generation of .NET development.
Unit testing in VS 2005 could be a mixed blessing. Not too long ago, a prominent .NET developer community leader and I were talking and he said that he felt that TDD could become largely irrelevant in the Whidbey wave because VS 2005 can generate test classes from your domain. Ok. Not necessarily a correct statement from TDD's perspective, but fair enough for someone who doesn’t do TDD. It's a bit unnerving that the assumption of the nature of TDD was not just a little wide of the mark, and there didn’t seem to be any real recognition that it might be a valuable to gain some TDD experience before coming to conclusions about the next stage of its evolution.
If every .NET developer who picks up a copy of VS 2005 and starts unit testing claims that he is doing Test-Driven Development then we're going to have to wait for the over-estimation and the denial to loose hold before this extraordinary and rather revolutionary programming practice can take hold in .NET culture. And in that in-between time, TDD practices and concepts might become so watered down that the methodology could simply fade into the annuls of dead languages mired in ambiguity, jargon, and colloquial misuse.
Test-Driven Development is a design discipline. TDD is a rigorous programming methodology that brings the levels of formalism previously only found in OO analysis and design methods down to the level of designing and constructing code and into the hands of the programmer. Unit tests are a side effect of TDD.
If you're generating tests from pre-coded classes, chances are you're not doing TDD. If you're generating tests from pre-coded classes, and you've never really done any unit testing before, and you assume that you're doing TDD, chances are that TDD is going to go sailing right over your head, maybe never to be seen or heard from again.
TDD is design. Say it ten times. It even rhymes. And now good-bye.
