TDD for unbelievers
The Agile Zone is brought to you in partnership with JetBrains. Learn how Agile Boards in YouTrack are designed to help teams plan, visualize and manage their work in an efficient manner, with support for both Scrum and Kanban processes.
I have to admit, I was not-so-good with TDD (Test-driven development). I just didn’t see how it can help me to deliver a better software. Products and solutions I developed were pretty much rock solid and it felt good to see them running for years. What else did I need?
But it was time to give TDD another try. Obviously, it became quite impossible to ignore this subject today, especially when people talk about continuous deployment already!
“Real Developers Don’t Need Unit Tests” (video) does a good job in describing once again what TDD is good for. If you don’t see a value in TDD, like I did – please, do yourself a favor and watch this session.
Some major takeaways:
- I guess the main point is that TDD is not about writing failing tests first, as I assumed for years. It is a way to design and implement a system, not from one’s head or some silly design specification but from the actual needs. Tests are a pleasant side-effect, but what they really do is actually show how the system works. I think it was “Groovy in Action” that initiated an excellent tradition of using asserts as a way to demonstrate what code does. What describes better the end result?
assert "test" == str.test() // Or str.test() // prints "test"
It may sound like an obvious thing but making a mental switch from “Tests for the sake of tests” to “Design and implement through tests” was my first enlightenment as I watched the video.
- There are three phases in doing TDD: Red, Green, Refactor. First, you provide a demonstration of a new feature. It takes a form of a failing test, right. But it is a demonstration of a new feature nevertheless. Then you implement it. And then you clean up, refactor and tidy up. The cleaning process is a critical one! From my experience it is widely and commonly omitted as people get to the “Green” phase and commit. This is exactly where products start getting a bad smell, clarity get lost, things get messy and next thing you hear is “Do not ever touch it”.
- Test behavior, not implementation. Another common belief about unit tests is they should test class methods and state transitions, i.e., current class implementation. The problem with this approach is that tests break each time implementation changes. And instead of updating tests accordingly it is very tempting to comment them out, <exclude> them from running or simply @Ignore. If one tests functionality and behavior then it shouldn’t break! If there’s a need to test a private members then they probably do too much and should be extracted to their own class and their own tests. “Design and implement through tests” again.
- When doing code review start from the tests. Since tests are the answer to the question “What this code does?” it makes sense to review them first to concentrate on a meaningful parts instead of everything. Especially when time is tight and only the relevant code should be reviewed.
- Unit tests are not a substitution to integration tests, acceptance tests and QA processes. As was mentioned above, TDD doesn’t really serve the purpose of testing although it is called this way. It doesn’t test for real and doesn’t try to find bugs, it only demonstrates application’s ability to do something.
- Treat your tests as production code. Tests should not be written as a throw-away code that is never maintained or updated. Tests are application’s user interface, its front page, if you wish. And we wouldn’t like our front page look ugly, would we?
Now, what really stops me from doing TDD starting from tomorrow? I would say a lack of commitment of everyone involved. To integrate a real TDD process into organization is very costly. It will take months until all infrastructures are set up and people stop spending a noticeable extra time on writing tests. Initially it creates a huge overhead, especially when dealing with legacy code. Eventually, an extra time spent on writing and maintaining tests pays off by producing a code that is better designed and is of much higher quality. After all, that’s how people get to continuous deployment that I mentioned above – they started with something, right?
Those willing to pay the extra price of doing TDD can get to it. Those running after new features every two weeks will have a harder time, of course.
See a follow-up post “JUnit Updates”.