If there ever has been a term that has been wrongly used and has been misunderstood often it has to be the term TDD. Often I encounter teams or read about teams that proclaim they practice the ancient art of TDD. Well let me tell you a little secret : a lot of those teams and developers do not do TDD!
Somehow the practice of test-automation and TDD are being used interchangeably and get mixed up all the time. It is not unlike the confusion that exists with the term unit test. The term unit test is being used as a synonym for an automated test, but an E2E test is an automated test, but it is definitely not a unit test.
So what is the confusion all about? TDD stands for Test Driven Development, and the word to remember here is driven. TDD is a unique way of creating your software. No code will be written unless it is preceded by testcode. So ? Just write down all your tests you can think of when reading your user story and start coding your product. No, that just won’t cut it.
TDD has a very characteristic cycle of development, a cycle where you gradually add tests one after another until you reach a certain end state. You start of on your blank canvas with a single test, which can be incredibly simple.
Imagine you are coding a word counting routine. You just pass some text to the routine and it has to count the number of unique words in it. Now before you write the code, you first start of with the simplest test you can imagine, often referred to as the NULL test. This test could be something like this
- When an empy list is being processed it should return a count of zero
That’s it. Now run your test and it will of course fail, since you do not yet have a system under test (SUT). Now you start writing your production code. When you write your SUT, you only program what your test requires you to do! All other requirements will follow later, hence the name test driven development.
public int CountUniqueWords(string text)
Whenever I give some training to developers that have no experience with pure TDD, this is where they start to revolt. The reason for that is they are already ten steps ahead in their mind. Normally they try to program all the requirements of the user story all at once. Sometimes they even try to build in the requirements of a user story that is due in the upcoming sprints. Now that is something that should be prevented at all times but you can imagine their reaction: that’s all the code you write? Yup. That’s the only test you have. Run your test and you’ll see your green checkmark. Mission accomplished.
Next step is to add another simple test. You progress in small steps one at a time. I can imagine the test would be something like :
- When the text only contains one word it should return a count of one
Now run your tests. One of them will fail, one of them will pass. Now your mission is to get two passing tests. Here is the resulting code :
public int CountUniqueWords(string text)
Again revolt. By now the group of developers really want to do you harm and form an angry mob. There is no way this code will count words! Well, just run your tests. Two green checkmarks will tell you your code is working like a charm. Now this is essential to TDD, you change your previous code to satisfy the tests, nothing more and nothing less. To be specific, you first write some quick code to make your tests succeed, afterwards you refactor to clean up your code. Now you are up for the next test :
- When I supply a text with two different words the resulting count should be two
Of course this is where it gets a little bit more complicated but be aware of the the pitfall. Do not code anything about the uniqueness of the words, you will deal with that later with a test like :
- When I supply a text with two similar words the resulting count should be one
You get the picture by now. So TDD has a unique cycle of starting of with a failing test, write some quick code to make your tests succeed and then refactor your code so it meets your coding standards while maintaining those green checkmarks in your test runs. Next, add another test that fails, write code and refactor again and so on. Until you reach your complete set of test and your function is complete.
So I hate to burst your bubble, but if you write your code first and your tests second, you are not doing TDD. TDD is a different approach to designing, testing and writing your software, it ensures that you have a complete set of tests (if you have read your user story carefully) and you meet all the requirements that were set by the user story. It really integrates code and testcode, one cannot exist without the other.
The reason that it is not practiced as often as it is claimed is that developers often have the habit of programming all requirements at once and then unit test. Most of us have learned to develop without even taking unit tests in consideration. Once the SUT code has been (presumably) finished, then all tests will be written and run one by one. Now the big difference here is of course that you might be in for some surprises at a late stage in development and you have to do extensive refactoring afterwards. There is also the risk of skipping tests and missing coverage. Now old habits die hard, so changing your method to TDD is quite a hurdle for most developers. Now it has long been debated if TDD is a slower process than just unit testing afterwards, but I want to stay away from that discussion here. In reality both methods are being used in teams with success. I believe that TDD is a more precise way of working, whereas unit testing after writing your code demands more discipline and experience of the developer.