Simply said, TDD is the practice of writing tests first before implementing the code. It starts with writing down the code behaviour we wish to have as tests, followed by the implementation. This is done one step at a time, switching between tests and implementation. In TDD, we let the tests guide the implementation, which means that code is written according to the requirement - not more not less.
What are the benefits of TDD?
- Boost confidence in code that works
- No fear in making changes to the code. Tests will tell if behaviour changes.
- Code becomes more flexible, maintainable and reusable.
There are multiple ways to describe a TDD approach, here are 2 famous ones:
The three laws of TDD by Uncle Bob:
- You may not write production code until you have written a failing unit test.
- You may not write more of a unit test than is sufficient to fail, and not compiling is failing.
- You may not write more production code than is sufficient to pass the currently failing test.
Rhythm of TDD by Kent Beck:
- Quickly add a test
- Run all tests and see the new one fail.
- Make a little change.
- Run all tests and see them all succeed.
- Refactor to remove duplication.
Following these guidelines, coding becomes a series of short bursts of writing tests followed by writing code that pass the tests, also known as the Red Green Refactor cycle.
Red Green Refactor Cycle
- Red - write test that fails
- Green - write code to pass the test
- Refactor - remove duplications
The Red Green Refactor cycle concisely summarizes the previous concepts by Uncle Bob and Kent Beck. We start by writing a failing test which describes the basic behaviour of the code we wish to implement. Then we write just enough code to pass the test. After that, we refactor the code to keep it clean while still passing the tests.
The next cycle begins with adding new tests to describe the desired behaviour in a wider range of scenarios. Next, we write code that will satisfy all test scenarios. After the all the tests are passing, the code may contain duplications and likely to be in need of refactoring. The tests give us confidence that refactoring the code will not change any behaviour, giving us the ability to clean up the mess we made while implementing the solution.
How to get to green?
- fake it - use constants with expected result
- triangulate - drive abstraction with increasing number of tests
- use obvious implementation
As more tests are added, constants that passed initial tests fail. Code becomes more generic and abstracted to satisfy wider range of tests. Eventually the implementation evolves towards real implementation.
There are a few practical techniques that can be used to get into the motion of TDD:
- Baby steps - start small, take small steps. Start with the smallest and most obvious tests.
- Ping pong - pair up and alternate between writing tests and implementation. Person A writes one small test, Person B implements then refactor. Person B writes next small test, Person A implements then refactor. The cycle goes on.
- Test for edge cases - null input, empty strings, 0, very large number, very long string, etc.
Things to keep in mind while writing tests:
- Keep the tests clean, as clean as production code.
- Use meaningful test function names and descriptions.
- Tests also form the documentation, so use good descriptions and assertions.
Things to watch out for:
- If tests are not kept clean, they will be neglected and new tests will not be added, thus taking away the purpose of tests.
- Large tests - tests that test more than 1 behaviour.
More advanced testing concepts:
- Test data
- Mocks
- Feature tests
- CI
Reading Materials:
- Test Driven Development: By Example - Kent Beck
- Clean Code. A Handbook of Agile Software Craftsmanship - Robert C. Martin