What's Wrong with Test-Driven Development (TDD)
A while ago I was asked to talk about the problems of using TDD – being
me I’ve decided to do the exact opposite, this session was names “what
is wrong with TDD”.
I felt that one of the major issues is that TDD looks weird, it’s counter-intuitive, and convincing developers to actually try it hard and requires a mental leap of faith.
And so I’ve created the talk aiming to help those developers who wish to teach their team a new methodology (because TDD is a tool not a religion) and need “ammunition”, answers for their co-worker’s questions, suspicions, rants and excuses for not using Test Driven Development.
Since then I got to present this talk several times both as 15 minutes lightning talk as well as a 2 hours session (and anything in between) – and so I thought to write a blog post that would summarize these sessions on the topic of reasons of not using TDD and why they are wrong…
I don’t like tests/I don’t have enough timeI believe TDD and “unit tests” has been done a great injustice by not giving it a cooler name – preferable one that doesn’t have the word “test” in it – because it’s a PR disaster!
A few years ago I have been invited to talk about “TDD in the real world” at a local user group – around the half hour mark one guy who set at the front row raised his hand and asked me – “does that mean that developers write these tests?”
It’s easy to understand why some developers think that “tests” are not their job – after all they are called “developers” and not “testers”.
Usually this lack of enthusiasm of writing these strange little tests hides behind a more “acceptable” argument – I really want to write unit tests (wink, wink) but unfortunately I do not have enough time to do so – obviously such developers are developing the only project in the world that have a deadline.
There is truth in the “I don’t have enough time” argument – writing code with unit tests takes more time then writing code without, I call this development phase as the “hitting keyboard phase” and it could take from 15% to 35% (!) more time – even more on hard to test projects.
But the benefits of TDD are much greater and in fact using TDD we reduce the time it takes to get your code to the client (the one who uses your code) by having less bugs (around 40% to 91% less bugs) and more maintainable code. I wrote about it back then when I worked at Typemock - The Cost of Test Driven Development.
[Realizing quality improvement through test driven development: results and experiences of four industrial teams]
In conclusion – Writing tests costs time, but overall development takes less time. It’s an investment and like all investments you can choose not to make it – it’s up to you. Just make sure you’re aware of the cost of that decision.
Writing tests before the code is counterintuitiveSince most developers cannot predict the future – how can you know which tests to write before actually writing the code?
I think there are two real reasons not to want to write tests before the code:
It requires leaving you comfort zone
Up until this point you were taught to think of all of the possible scenarios and find the optimal solution, using the power of design methodologies learnt during a long and successful career. And now you need to “not think about design” and develop one test at a time – an it’s suppose to fail!
Planning for failure
Deep down it’s hard to see something I wrote break – even if it’s just for a little moment. And so writing a failing test a.k.a the first step of TDD is hard for some.
What would a developer not willing to try (exit comfort zone) do? Write unit tests – after writing the code and try to sell this practice as TDD.
This is usually a bad idea – most experienced TDD practitioners can tell whether or not the unit tests has been written before or after the code. And writing unit tests for existing code is harder, much harder than writing the tests before.
A developer who write unit tests after writing his code is missing the whole point – TDD is a design methodology – the unit tests are just a by-product of the process.
It’s about growing your code writing only what you need and about emergent design and not about writing unit tests.
Not everything is testableThis is actually a good reason – when this is really the case.
There are pieces of code that cannot be run as part of unit tests. I’m not talking about bad written code – there are ways to handle these. I’m talking about UI, video streaming, and some closed architectures that make unit testing almost impossible.
Needless to say if you cannot write unit tests – you cannot use TDD (duh).
But make damn sure that this is the case – because hard to test is not untestable:
- By following MV* patterns (MVP, MVC, MVVM etc.) you can still test the UI business logic and drive its design using TDD.
- Mocking frameworks are there to help you stub/mock external dependencies (Web, DB, 3rd party components)
- When all else fails write integration tests (yes – as part of TDD)
In the end you might not have 100% test coverage (whatever that means) but at least you’ll be able to easily maintain and develop your business logic.
TDD locks designI get this usually half way through teaching a unit testing/TDD course - what would happen if we write all these tests and then need to change the design?
It feels like a big waste of time re-writing tests over and over again just because code (being code) was changed to fix a bug or add functionality.
Once I even got a complaint by a co-worker that “writing the code took one hour but fixing the tests took half a day”.
The good news is that there are principles of good test design that would prevent having to change your tests every single code change – this is where one assert per test and don’t test private methods come from (and there are many others).
Ideally only requirement change or a bug should cause your tests to break. This is not always the case but after writing a few hundred tests, reading a good book (or blog) you’ll find what to avoid and how to reduce the cost of maintaining your unit tests.
Good tests are simple, robust and easy to update if the need arise – bad tests get deleted and replaced.
ConclusionTDD is not perfect:
- It requires time investment
- Seems counterintuitive at times
- A new skill to learn
- It might not work for all scenarios