TDD process & code smells
- Processes, standards and quality
Test-Driven Development (TDD) is software building technique which allows tests to „drive” development process. The idea is quite simple:
- write test first (for small part of functionality that you are going to implement) – test have to be red – failing from known reason (RED phase)
- make test pass – write the simplest possible piece of code that makes test pass (GREEN phase)
- refactor – both test and production code
These three steps are repeated until whole required functionality is implemented.
If you are not familiar with TDD, you can go for short introduction to Martin Fowler’s blog: http://martinfowler.com/bliki/TestDrivenDevelopment.html
If you want to extend your knowledge in this topic, I recommend you Kent Beck’s „Test Driven Development: By Example”.
What could go wrong?
TDD is complex process that consist of four related activities:
Although idea of TDD is quite simple, it is not easy to do TDD well. Even single mistake may have negative impact on each aforementioned activity. As a result TDD does not work for us – it does not provide promissory benefits.
The rest of this post is a list of easy to catch “smells” in TDD process and created code along with recipes how to fix them:
No test at the beginning
Surprised? So let me give you short explanation: I do not mean here switching definitely to Plain Old Unit Testing (POUL),
or Test-After-Development (TAD).
I mean two situations:
a) skipping writing test first sometimes when it is „convenient”:
- test is too difficult to write
- functionality being added seems to be too trivial to be tested
Using TDD you must always write test first. Only then you could gain main benefits which TDD offers:
- safety – test written before implementation will never be false positive. It will test exactly what it should. It will also guard further refactoring of code
- good design – „test first” means also “reflection first” and good design as a result. When it is hard to write test, you may be almost sure that something wrong is with design of your application/class/method. TDD allows to discover such problem quickly
- documentation – unit tests written in TDD process are complete documentation of production code. Skipping test first may lead to incomplete documentation
- fun – creation of tests in TDD is an important part of exciting, creative process. When you skip test first, you will be writing it after – probably without great enthusiasm 😉
b) skipping test first mentally
I write test first, because I should. But I know exactly what I’m going to do in next 20 steps. Tests are like a fifth wheel to a coach. Such approach means that I’m technically switched to TDD, but mentally faraway from its main idea.
This way we lose some of the benefits of TDD. Such attitude may also lead to other process smell described in this post: too big steps.
Skipping RED phase
Never write a single line of code unless you have a failing automated test. (Kent Beck)
Each test have to pass through following 3 (or 4) phases:
- setup (given)
- exercise (when)
- verify (then)
- (teardown, as tests have to be independent)
Failing test is not a test that does not compile. Failing test is test that does not meet condition(s) from verification phase!
We can assume that red phase has been skipped in following situation:
- test does not compile
- test has not been run before creating implementation that satisfies it
- test is broken, which means that does not fail from specified reason
So, correct sequence of actions in one TDD cycle is as follows:
1. create unit test for small piece of functionality being implemented
2. run this test – it must fail for specified reason
3. implement tested functionality
4. run test – it must pass
5. refactor production and test code
6. rerun test– it must pass
Unappreciated refactor phase
The most common way that I hear to screw up TDD is neglecting the third step. (Martin Fowler)
The third step in TDD – after RED and GREEN – is just REFACTOR.
Even small steps along with deep thinking do not guarantee perfection. Refactor phase is perfect time to take a step back and try to look at broader picture, but also to make some (seemingly) minor corrections .
It is important to take care of your code, to make it:
- readable : „code should read like a well-written novel” (Uncle Bob)
- self-commented: „comments are always failures” (Uncle Bob)
Remember also that test and production code should be treated with same attention.
It is beyond of scope of this post to give you more information about refactoring. There are some shortcuts which you should have always in mind (along with rules behind them): SRP, OCP, LSP, ISP, DIP, KISS, YAGNI, DRY… There are also books which may increase your knowledge and skills in this field (I can recommend “Refactoring: Improving the Design of Existing Code” by Martin Fowler).
Too large steps
It is really hard to tell how small should be a single step. But we can formulate several general rules:
- it is better to make too small than too large step
- the less experience you have in TDD, the smaller step you should take
- if one TDD phase takes more than 10 minutes, step is probably too large
- if tests are too extensive, step is probably too large, but in this case problem may be more serious
Extensive unit test is always hint that something is wrong with TDD process and as a result with our code. There are two main factors that makes test too long and too complex:
- difficult initialization (large “given” section) – indicates that something wrong is with design of your application (tight coupling, low cohesion, overused singletons…)
- to many assertions (large “then” section) – indicates that our code breaks SRP (Single Responsibility Principle), or we are checking too many not important details.
Goal: code coverage
Forget about code coverage metrics in TDD process. TDD gives very high coverage by default and guarantees that whole functionality is well tested.
Do not create additional unit test too gain 100% coverage. Improve your process instead!
- keep applying main rules strictly
- examine your process and code to discover and fix quickly aforementioned smells
Have a lot of fun with TDD!