Test Driven Development by example
TDD is not about having tests with your code. It’s not about writing tests before writing code. It’s about driving design and splitting coding process into bite-size steps using tests as drivers.
Just a simple example how test-driven-development works.
The goal is to write a function to concatenate two strings. Assume that an input is two non-null strings and a result is also a non null string.
Let’s have our first test for the second part of our statement - non null result:
Although, the test is trivial, it already contributes to the design. The test has an expectation to the function interface: the input is two strings and the output is a string as well.
This implementation makes the test happy:
Although, the test is green now, the code above is not TDD. The problem is that the code is way too complicated. Here is a better implementation:
The test is green as well. But look into the implementation - it does exactly what the test expects and nothing more - the implementation returns non null result. It’s is important to mention that first implementation is not just complicated; it has a code path without any test coverage; for example, both “a+b” and “b+a” would keep the test green.
This is, probably(IMHO), the most important aspect of TDD, to make a test green, a developer must take a shortest path.
Now, when we have the test green with minimal possible implementation, it’s time for the next step. Make tests:
The test set from above is a full specification for concatenate() behaviour. It covers all edge cases as well. Why no null input scenario? The interface description states that nulls are not allowed. If someone will send a null, Null Pointer Exception will be raise, and this is ok (defensive programming is a topic for another day).
To make tests from above green, the function’s body get modified to:
Another key point for TDD is the size of each step. Sometimes, a simple test case for a + b may result in a heavy development; imagine that strings must be validated by a remote service. For this cases, current test should be postponed and underlined features must be developed in TDD style. (Postponing is very convenient with Git branching)
Summary:
- Give descriptive names to tests
- Write test for a specification or a feature first
- Cover entire specification and all edge cases
- Make MINIMAL possible implementation to satisfy tests
- With everything green, refactor
- Repeat