Why Test-Driven Development Is Like Surgery
There is one thing in common between Test-Driven Development (TDD) in programming and antiseptics in surgery – both concepts had had their opponents before the public got a chance to recognize their positive effects. The difference is that some developers still don’t appreciate TDD. For others, it works. How come?
The goal of antiseptics was to kill germs and bacteria to avoid spreading them between live tissues – which would result in the development of diseases – during medical procedures, especially in surgeries. The first promoter of antiseptics was Ignaz Semmelweis in 1847, trying to convince the medical community to notice its effectiveness. No matter how much evidence he gathered, other medics rejected the method. Many years later, antiseptics made a comeback and have been used at hospitals and clinics as a standard.
Wait, what? What does it have to do with TDD?
More than you can imagine.
What does “clean code” even mean?
‘Clean code reads like well-written prose,’ they say. When I was learning TDD, I wondered what that meant. Now I know how it looks and how eye-friendly it is.
Just imagine your code without duplications, unused or forgotten lines. No TODOs or FIX ME annotations – one of the most desired goals of every developer. With Test-Driven Development, you won’t have to be ashamed of any line of your code anymore.
Clean code reads like well-written prose.
In the ideal TDD-powered world, QA acts only as a final confirmation that the application works well. There should be no tickets returned. I know, it sounds like a perfect scenario that will never take place in the real world, but TDD can get us closer to it, so why not try?
The only way to test lines of code is to access them from tests, but the only way to access them from the tests is to decouple the functions that contain them. Decoupled code keeps a high level of separation and interdependence between app modules.
As the tests get more specific, the production code gets more generic and becomes more decoupled.
Do you need anything special to get started with TDD?
Yes. You need to do one of the most important things that have affected people in all aspects of life, of all nationalities, religions or location – overcome your fear and take the first step. Sounds like a cheesy cliche from a life coaching offer? Well, this is the naked truth!
Your first step is a decision to write tests, maintain your code in order and leave it in a state you can be proud of. Overcome the fear that you might break something, that you’ll be responsible for any piece of code you touch. That code is yours. If you mess something up, you’ll need to handle the angry looks from other developers. It’s you who broke the code, now fix that, stupid! Uncle Bob calls it Code Rots – the parts of code that actually stink, or even rot, the parts that nobody wants to touch because it’s better to stay away from such spots.
TDD should be your discipline – your daily routine, without which you cannot start working. Following a Red-Green-Refactor flow should be the same process as the one you follow during your work in a Scrum process:
- Create a user story
- Send for code review
… and so on. Thanks to TDD, you don’t have to do any additional estimation of your work – it should be a part of your basic process.
Apart from that, you don’t need anything else. 🙂 You can start TDD no matter how experienced you are and what tools you’re using. There are plenty of ready-made solutions to help you in the process.
What are the rules of TDD?
TDD is like mountain climbing. If you have ever climbed, you know how important discipline and focus are. You need to be sure to get three stable points you can set a base on. If any of these points are weak, you feel fear and insecurity – and you are petrified to move on. But, do you remember? TDD is a technique that removes your fear. So you need to consistently follow these rules of TDD.
As a discipline, it has certain rules:
1) You are not allowed to write production code until you have first written a failing unit test.
Write NO production code except to pass a failing test.
2) You are not allowed to write more of a unit test than is sufficient to fail, and not compiling is failing.
Write only enough of a test to demonstrate a failure.
3) You are not allowed to write more production code than is sufficient to pass the currently failing test.
Write only enough production code to pass the test.
All those rules are combined into a Red-Green-Refactor cycle, which is a core TDD process you need to follow:
- Red phase – create a test and make it fail
- Green phase – make the test pass by proper implementation – as quickly as possible. The green test bar should be your most desired destination so far.
- Refactor phase – remove duplication and improve design and readability of your code. Make sure that any change keeps the tests passing. It takes just a second to run a suite of tests after every change you make.
Do I still have to do debugging?
TDD eliminates debugging from your development process. It doesn’t exist, because there’s nothing to debug. Now you can spend all that time you wasted on debugging in a different and probably more pleasant way.
How do I pick the right architecture?
The right software architecture is supposed to provide you with the best possible level of scalability and mobility. It’s not an easy choice to make. You may wonder: Which pattern should I pick? Which one will work better for me?
TDD tells you: forget about architecture!
Of course, this is not true at all, only in the beginning. First, just start writing tests, don’t worry about the final form of your application (you could even start writing tests and application logic in one file. What??!!). Do you remember this architecture creating process when a group of software architects comes to you with a huge UML diagram with a lot of arrows showing dependencies of components? And this doc with the diagram was really important in later development process. Forget about it. When getting started, just take care of your code. Make sure it’s readable, easy to test, free from duplications. You will see that your architecture comes into shape by itself, and it is easy to put into a final form.
How about documentation?
The tests are the actual documentation of your code; you call it a low-level design documentation. By reading the tests, you know what you may expect from the production code. It’s a great form of documentation for programmers, because it’s written in a language they know perfectly and that they use every day.
When you get to know a new API, framework or library, the first thing you do is look into documentation. But what exactly is valuable in documentation? The examples of usage in code. Tests are an equivalent of such documentation, presenting real-life usage examples.
Misconceptions about test-driven tevelopment
TDD is a waste of time.
No way. Debugging is a waste of time. The amount of time wasted on searching for the faulty spots and the level of frustration you reach on the way… TDD is not that much extra work. Once you tap into the rhythm, you will never treat it as a waste of time.
TDD protects you from stupidity.
TDD is not a cure for all diseases. It’s not a perfect solution either. Practicing TDD doesn’t protect you from making mistakes. It’s an art of writing code that needs skill and experience that you gain only through practice. TDD is a sort of a parachute – when you climb up the mountain, it’s good to have one at hand and open it in case you feel weak or slip. But parachutes can’t replace elevators to take you to the top, right?
TDD slows you down.
Writing tests requires – surprise, surprise – writing more code. Writing more code doesn’t happen in a snap. So, in a sense, it’s true that you need more time to write production code. But it feels like you slow down only in the beginning. In time, you’ll develop a habit, a discipline, and you’ll start your work with writing tests naturally.
Pro tips for Test-Driven Development
Any of the test methods should depend on each other. When one of the tests breaks, then you have got only one place where you need to perform the fixes.
Choose the proper scope of testing – it should be smaller than the whole application – and test in the context of a class or package.
No matter what the execution order is, the results always have to be the same. When the tests are decoupled, you structurize your production code and make it decoupled.
Put assertions first
What do you expect as a result of your test? Start writing the test scenario by defining the assertion.
Tell the story behind the test
Try to define the story behind the tested functionality: what is the input/output, what is the purpose of this functionality? Be straightforward; use common and simple words.
Use handy data
Define the data you are going to use in the test. The data should be easy to analyze, and the differences between used inputs should be clear.
Start with the basics
The first test should be the simplest check that you can imagine. Try the most obvious scenario that you can put into only one step.
Use long test method names; use comments if you think that something is unclear.
Operate on mock objects and define the inputs and results that you need to provide, but remember – do not mock everything!
If you choose the path of TDD, it should become your daily routine, just like washing your hands before a meal. Do you use TDD in your everyday work? Or maybe you can’t imagine yourself working like this? No matter which side you’re on, share your thoughts below. Interesting conversations don’t start by themselves!