Many developers are claiming to be TDD practitioners, without really understanding the differences between Test-Driven Development and unit testing. Somewhere along the line the definition of TDD has gotten lost and it is time to set the record straight; just because you write unit tests does not mean you practice TDD.
Unit Tests
When doing unit testing you want to isolate the smallest possible unit of your application, usually a class, and test it in complete isolation. This means that you need to be aware of any external dependencies that the unit under test has, so that you can create an abstraction between the unit and the environment around it, usually by taking advantage of mocking framework or fake objects.
Without isolating the unit from its external dependencies you would be creating integration tests. An integration test asserts that the interaction between two or more units produces results that satisfy functional requirements. These types of tests are completely different from unit tests, but are equally important.
A unit tests should not be concerned with the internal implementation details of the unit under test. Testing implementation details such as internal members or state is a code smell and is almost certain to produces brittle tests; tests that break when you make seemingly unrelated changes. If you find that you keep having to rewrite your tests when you update the internal implementation of your units, then you are probably heading down a terrible path and should step back and reconsider the way you are working.
Instead a unit test should only be concerned with testing externally observable behavior of the unit. This does not only mean the public API of the unit, but also behavior that can be observed through the dependencies of the unit. What this means is that the unit under test might be calling out to one of its dependencies and that call can be observed from outside of the unit.
One thing that you need to tend to when you are writing unit tests is to make sure they have no side-effects. A unit test should run in complete isolation of each other and the order in which they are executed should not have an impact on whether they fail or not. This could happen if the units have not been isolated and one test alters the state of a dependency (for example a database) in such a way that another test will fail because of it.
I recommend that you read ”What is unit testing?” by Justin Etheredge if you want to learn more about the characteristics of a unit test.
Test-Driven Development
The one thing that TDD has going against it is its name. Because it has the word ‘test’ in the name it is often misunderstood as being a more fancy way of saying that you write unit tests for your code, nothing could be more wrong.
So exactly how does TDD differ from writing unit tests? Strictly speaking from a testing perspective, not much. You use the same techniques and tools to write your tests. Both tests are unit tests and both tests are interested in the externally observable behavior of the unit. The difference lays in the goal of the tests.
TDD is not about testing, it is about how you develop software. The goal of TDD is to produce code of high architectural quality by letting the tests drive the design of your code. This done by applying what is known as “Test First”, where you write the test case before you implement the code that will make it pass. This forces you to really think about how the implementation should work, instead of writing a test to make sure the code you already have in place works.
This is where the ‘test’ part of the name skews many people’s conception of TDD, because it is not really so much about testing as it is to write down (executable) specifications on how the code should behave. Got that? First you write down your specification on how it should work, then you write just enough code to make it work.
Because of the naming confusion people like Martin Fowler suggested it should be called Specification By Example and Brad Wilson (and friends) started calling it Design By Example, both being far more descriptive of the real intent of TDD.
When writing code using TDD you follow a what is known as the “TDD Mantra” also referred to as Red – Green – Refactor. This breaks down to a very simple development cycle.
- Write a test that captures the intent of the code
- Run the test to be sure it fails
- Implement the least amount of code required to make the test pass (do not take design into account)
- Run the test to make sure it passes
- Re-factor the code into a more elegant design
- Re-run tests to make sure the refactoring did not break anything
By starting with writing the test first you really have to think about the intent of the code you are going to implement. You run the test to make sure you do not get a false positive. Next you write just enough code that is required to make the test run and verify the implementation by running the test once more, making sure it does not fail this time. Once that is done, you refactor your code to a more elegant design, taking full advantage of all your code design skills and practices. After you have cleaned up your code it is a good idea to run all of you tests again to make sure you did not break anything else in the process.
If you stay true to this development cycle you will realize that you are no longer adding code based on assumptions, but instead only add the code that your specifications require. There are probably not a developer alive that is not guilty of adding that extra method overload or property just because it could turn out to be useful. Instead of being useful it could turn out to be a maintenance nightmare and because of backward compatibility issues you may not be able to remove it later on. Scott Bellware talks about this in his ”TDD is about not knowing” post.
Assumptions really do have a huge impact on code design even though we do not think much of it. Even if you were to follow the TDD development cycle you are still going to be putting a great deal of assumptions into your design. It is in our nature as humans and, most of all, developers. Keith Braithwaite created a workshop titled ”TDD as if you meant it” to challenge our perception of how many assumptions we really put into the design of our code.
Gojko Adzic attended Keith’s workshop and blogged about it in a post entitled ”Thought-provoking TDD exercise at the Software Craftsmanship conference” and later on Mark H. Needham did a ”coding dojo” on the same subject. I encourage you to visit and read those posts, even if you are a seasoned TDD practitioner, I’m sure they will challenge your perceptions.
Even though TDD is not explicitly about testing, the tests still have some quality assurance values. For example they are still a prime candidate for automated regression and acts like a safety net when you refactor your code to reach a higher degree of architectural quality. Be sure to treat the tests like first class citizen in your project and take the time to keep them up to date and clean. Avoid commenting out a failing test because you feel you are under time constraints or with the notion that you will be coming back to fix the test at a later stage.
The tests are your specifications. If your specifications are not up to date and correct then there is no way to tell if the code you are developing is the code you should be developing. You would not treat a formal specification document with such disregard would you? If not, then why would you want to do it with an executable version of them? They are your friend, they will keep you out of trouble and out of the debugger. They are the first client that will touch your code and, if you take the time to craft them well, they will never lie to you. Embrace them.
Summary
I hope that it is evident what the difference are between just writing unit tests and committing to the Test-Driving Development. There is very little required to get started with TDD, if you are already writing unit tests then you have all the tools you need to get going. TDD is a discipline and as time passes by you will use your gained experience to hone your skills. Why not grab a couple of friends and setup a ”Code Kata”, getting other peoples insight is awesome whether you are just fine tuning your own skills or just learning from scratch!
You can follow me on Twitter @ TheCodeJunkie