Below are three goods things I like about Test Driven Development and three annoyances. I have tried to avoid any benefit or annoyance that relates to unit testing in general. We all know there are many of those. The pros and cons here are relate specifically to following the strict guidelines of TDD.
It takes out the decision of “how do I start?”
Sometimes you don’t know the best place to start when your given a project or feature to develop. By following Test Driven Development, there is one less decision you need to make. Your task is to write a test, not an entire piece of software. Just by following this discipline, we are already breaking our software in to manageable chunks.
Write a test, then write another test. Then another.
Let’s you think in terms of how will it be used rather than how should it work
I’m sure it has happened to all of us at some point. We build out feature to the point where it can handle everything we asked it to do, but what remains is a little bit ugly. We’re left with a method signature that takes a handful of objects and gives away to much about the implementation details.
This can be avoided. It starts with pretending like every time you write a function, it is going to magically do the thing it is describing. What would this function look like? When you start this way, you are only going to add complexity when it is warranted.
TDD drives this process by starting with the calling code first.
It prevents your project from getting away from you
We all do it. We write production code and get in a groove. We don’t want to break our train of thought by switching contexts to write the calling code. We assure ourselves that we will remember to go back and write some tests later to make sure the code is working.
The rules of TDD are to prevent you from writing too much code before its tested. Well it actually prevents you from writing any code before it’s tested. When you write too much code, it’s rare you will remember all the tests you thought you needed to write.
Adhering to “The simplest thing that will possibly work.”
Following the rules of TDD requires writing code that does the simplest thing that will possibly work. This is a tough one to follow. The simplest thing that can possibly work usually involves intentionally adding code you know in the long run will NOT work. It often gets you into trouble because the only way to catch the bug you just made is to make sure you have enough tests, but when can you ever be sure?
It requires the thinking “I know that passed test is wrong, I need to come up with a new test that proves it’s wrong”. You are in an awkward state in the development process here. If you take a break from your work for a second, are you going to remember you did the simplest thing that could possible work on purpose?
I understand the rule, it is to prevent your mind from getting way ahead. It prevents you from skipping steps. However, adding bugs so that they can be snuffed out later and removed is a slightly risky process.
The assumption of fast running tests
It is not often that you begin using TDD when you happen to be starting a greenfield project. More commonly, you know it’s a practice that has well known benefits and want to apply it to your current work.
The difficult part here is applying TDD to code that isn’t well built for unit testing. Even if it’s new functionality you are writing, you may rely on libraries that are relying on database or web service calls that you can’t do anything about.
This is very commonly overlooked, advocates of TDD are going under the assumption that the Red – Green – Refactor is a fast feedback loop. If you are hamstrung to legacy code, that is not fit to run frequently, your efficiency may take a hit.
Is testable code always cleaner?
When following TDD, the test dictates the API. This avoids writing new functionality and then getting stuck trying to create a test after the fact. The general belief is, testable code is quality code, so the restriction to developing in this order yields well designed software.
What this tends to give way to, is many injected dependencies. This is normally a good practice but an absolute requirement can lead to unmaintainable software. Quite often you have to convince yourself that “it will be cleaner in the end” but is it always?
We also know that we are not perfect as developers. We are going to take wrong turns that TDD won’t be able to correct. Much like deliberately adding the buggy code, when we know something doesn’t feel right, we shouldn’t continue blindly.