Canon TDD
![](https://habrastorage.org/webt/qp/aj/9k/qpaj9khgmgam9fdkzngzrm6j8ue.png)
Vic Wu (Vic Wu) managed to schematize the essence of this article.
In this article, I give bad advice on how to do TDD. Take responsibility for the quality of your work in any way that suits you, as long as you actually take responsibility.
This text is my response to comments along the lines of “TDD sucks because
- Write a list of test cases you want to cover.
- Turn exactly one item from your list into a real, concrete, achievable test.
- Modify the code so that the test (and all previous tests) pass (adding items to the list as they are found).
- Feel free to refactor to improve your implementation plan.
- Return to point #2 until the list is complete.
Introduction
In my recent explanations of TDD, I realized that people disagreed with the definition of TDD. In my book, I laid it out as clearly as possible and thought it was clear. It turned out not to be. Well, my mistake.
If you do something other than what's described below and it works, congratulations! Yes, it's not Canon TDD, but who cares? No one will get a bonus for following these steps exactly.
If you want to criticize TDD and not criticize the following workflow, you are engaged in a thesis substitution. This is the point of spending several precious hours of my life to write this article – to prevent false arguments. I am not teaching you how to program. I do not demand a reward for this.
I'm just trying to be generally positive and constructive. This post is going to be short and negative out of necessity, in the vein of “You're getting it wrong. This is how it really is.” I'm not trying to criticize anyone's workflow, but I do want to sharpen your understanding of the Canon of TDD.
Review
Test-driven development (TDD) is a programming workflow. The programmer needs to change the behavior of the system (which may be empty at the moment). TDD is designed to help the programmer create a new state of the system in which:
- Everything that worked before continues to work.
- The new behavior works as expected.
- The system is ready for the next changes.
- The programmer and his colleagues feel confident in the above points.
Separation of interface and implementation
The first point of misunderstanding is that people seem to lump all architecture together. There are two types of architecture:
- How a particular piece of behavior is triggered.
- How the system implements this behavior.
(When I was in school, we called it the logical part and the physical part, and we were told never to mix the two, but no one ever explained how. I had to figure it out later on my own.)
Steps
Humans are terrible computers. What follows sounds like a computer program, but it's not. It's written that way in an attempt to communicate effectively with people who are used to working with programs. I say “attempt” because, as noted above, people tend to say, “TDD is crap. I did
1. List of tests
The initial step in TDD, given a system and a desired change in behavior, is to list all the expected variations of the new behavior. “Here's the base case / this is if the service fails / this is if the key isn't in the database yet, and so on.”
It's an analysis, but it's a behavioral analysis. You think about all the different ways in which the behavior change should work. If you think about how the behavior change shouldn't disrupt existing behavior, add that in, too.
Mistake: Mixing in implementation design decisions. Relax. You'll have plenty of time later to decide what the internals should look like. You'll do a better job of listing tests if you focus on that alone. (If you need a Sharpie-on-a-napkin sketch of the implementation, go ahead, but you may not actually need one. Experiment.)
It seems that readers missed this step in the book. “TDD just moves into coding. You'll never know when you're done.” Nope.
2. Write a test
One test. A truly automated test, with environment setup, calls, and assertions (hint: try writing assertions backwards sometime). It is in the process of writing this test that you will start making architectural decisions, but primarily interface decisions. Some implementation decisions may slip through, but over time you will learn to avoid them.
Mistake: Writing tests without assertions just to achieve code coverage.
Mistake: Transforming all the items on a test list into specific tests, then running them one by one. What happens when passing the first test makes you rethink a decision that affects all those abstract tests? Mistakes to fix. What happens when you get to test #6 and don't see anything passing? Depression and/or boredom.
Choosing the next test is an important skill that only comes with experience. The order of tests can significantly affect both the programming experience and the final result. (Open question: is the code sensitive to initial conditions?)
3. Make the test pass
Now that you have a failing test, change the system so that the test passes.
Mistake: Removing statements and pretending the test passed. Make it look like it actually passed.
Mistake: Copying real, calculated values and pasting them into the expected values of a test. This destroys the double-checking that creates much of the validation value of TDD.
Mistake: Mixing refactoring with test execution. The problem of wearing two hats again. Make it run, then make it right (referencing Kent Beck’s Extreme Programming principle: “Make it run, then make it right”). Your brain will thank you in time.
If, as you move from red to green, you discover that you need a new test, add it to the list. If this test invalidates work already done (“cannot handle the empty folder case”), you need to decide whether to continue or start over (hint: start over, but choose a different order for running the tests). When a test passes, mark it off the list.
4. Refactoring at will
Now
You will have to make decisions about the implementation plan.
Mistake: Refactoring for longer than necessary for the session. It feels good to tidy up. It can be scary to face the next test, especially if you don't know how to make it pass (I'm stuck on this in one of my projects right now).
Mistake: Abstracting too early. Duplication is a hint, not a command.
5. Return to step 2 until the test list is empty.
Keep testing and coding until your fear of how your code behaves turns into boredom.
You can get more practical skills in testing applications within the framework of practical online courses from industry experts.