In my first conversation about iOS test driven development I discussed why we should bother writing unit tests in the first place. We found that if we don’t refactor the code then the code will become unmanageable and the only way we can keep refactoring safely and quickly is to have a safety net of unit tests. We also discovered that when you write tests after production code then you will often write production code twice and it ends up being slower. In this article we will discuss why I and so many others consider TDD to be an essential software development practice.
TDD isn’t just writing tests first; it has rules. When you follow these rules you tend to produce well designed, decoupled, high quality code. TDD provides a quick feedback cycle which helps you detect bad code quickly and greatly reduces valuable time spent debugging in the simulator.
You said last time that TDD is more than just writing tests first. What else do I need to know?
TDD has a few simple rules:
“You cannot write a line of production code until you have written a failing test.
You can only write enough test code to make it fail – not compiling counts as failing.
You can only write enough production code to make your test pass.”
I’m sorry I asked… Ok, so how do these rules help me to write better code?
The most obvious benefit is that you will have a reproducible and automated test for every piece of production code you write. In fact, that is a guarantee. TDD also ensures that your tests actually test something because you start with a failing test. How do you know that your tests work when you write them after if they have never failed?
That might be true but do we really need 100% test coverage?
I hope I’ve convinced you by now that you do if you’re going to keep a clean codebase. But also the greater the coverage, the greater the confidence. Are you ok with shipping an app that you are only confident that 80% of the code works?
Ok, ok, I get it; TDD means better unit tests and more confidence but what about good quality code? How is it supposed to help me with that?
Ever heard of red, green, refactor? This is the test driven development cycle. Red is making a failing test, green is making it pass. Then there’s refactor; this is your opportunity to perfect the code you’ve just written. Following this pattern will ensure you have sensible and readable code.
One of the challenges when working in teams is the ability to communicate your code to others. With this refactor-as-you-go methodology you find yourself making your code into small, clean and understandable modules which will help others to make sense of your code.
It leads you to good OO design choices too.
Ok, I’m skeptical about that one. Shouldn’t you be deciding your design before you write code?
I agree, thinking about design before you start is a good idea. Even flexing your UML muscles is useful for larger pieces of work. But TDD leads you to good design decisions.
The very nature of TDD means that you only write enough code that is necessary to pass the test. This often leads you to very elegant code. You might find that you over engineered a solution before you really understood the problem and the best way to understand the problem is to try and solve it.
TDD will also lead to decoupled code. Consider writing an entity A which depends on entity B. You will be writing tests to confirm that entity A interacts correctly with entity B. In order to verify these interactions the tests will replace entity B with a mocked version. This will force you to inject that dependency and will cause entity A to be decoupled from entity B. Testable code is another way of saying decoupled code.
You’ll also find that tests become more difficult to write as the responsibilities of an entity grows which makes it harder to violate the single responsibility principle.
The benefits are adding up right?
Ok, I can see those benefits but I want to go fast. TDD will slow me down.
One of the greatest benefits of TDD is the quick feedback cycle. How many times have you tested your code by running your app, navigating to your feature, pressing a button, and then testing the result? Be honest; we’ve all done it. Since following TDD I have found that I rarely run the whole app – perhaps once or twice towards the end of a feature. Instead, I continually run the tests. This speeds up the rate at which I can write working code but also prevents me from writing any bad code and then debugging it later.
Barely run the app?! And you’re that confident that your code will work?
I know the code works because the tests tell me so. Now I’m not Superman; sometimes I will make a mistake or forget to do something but those things are trivial to fix. These quick feedback loops really help me to move quickly and give a great sense of satisfaction when those red lights turn green.
The theory is all very well but I find that the way to truly understand something is to put it into practice. What are you waiting for? Just start doing it!
 Martin, R. (2011). The clean coder. Upper Saddle River, NJ: Prentice Hall.