TDD: Change the way you code!
Whenever we start a new software project as an organisation, most of the times we try to publish any new feature as quickly as possible. We work day and night to build features and take it to the customers. And why not, its the business goal at that point of time, which is going to put money into the bank accounts. Organisation hires more people to take more features / bug fixes out in the market. As lines of code grows, making change and building a new feature becomes more painful. Solving regression bugs takes more time than actually building a new feature.
This isn’t something new. We all know that. BUT how can we prevent it?
It has really simple answer: start writing Tests cases. “Ohh wow, you think i newer knew about it !” (This is the most common thought might arrive in our mind). We want to write test cases and we do write couple of. But it’s just so damn hard and time consuming that sometimes, to cope up with deadlines, we have to postpone it to another time that never comes.
Yeah its true. I have had the same feeling for my first three years of software development. Whenever i was given a task, my first instinct was to complete it ASAP because we were always short of time. Every time I make it a point to write test cases later at the end, had to skip it. There are couple of reasons for it: 1. Time was too short that couldn’t even finish development. 2. Writing test case becomes very hard as there are too many things to mock (Network requests, database operations, UI framework elements etc).
So now what, our only hope to write test cases is not a feasible option anymore 😢?
No. There should be a way, has to be. Let’s think if we can figure out something:
1. Why writing test case is a huge pain? → Takes too much of time.
2. Why it takes too much time? → Our code is not clean, we sometimes fail to apply separation of concern, etc. So we end up creating mocks.
3. Why we fail to apply clean code and separation of concern etc? → We do our best to apply everything pertaining to good practices / guidelines / standards etc. But its natural to overlook things sometimes.
So all we need is a
validator that checks for clean code/separation of concern / best practices violations.
What if we use test cases as validator? We will just write scenarios as empty test case at first and as we write implementation we will update test case to match its output.
Wait… How could Test Cases work as validator here? It’s simple, we are writing test case and implementation together, so the very moment when we need to create unnecessary mocks, we know the culprit. Wait… doesn’t it look familiar? Yes, this kind of practice we know as TDD (Test Driven Development).
Please note that, I am not saying that Mocking is bad or it shouldn’t be done. Mocking is my best friend when writing the test cases. But it becomes huge pain when i have to do a lot of them. For example: Mocking static methods, mocking constructors, mocking properties etc (God knows what more to come 😜). And this is the main culprit that holds us from writing test cases. Just imagine a world where you just have pass inputs to a test method and assert with expected output. Wouldn’t it be great 😊.
So for the all developers who think TDD is tough, impractical, does not work (blah, blah): it’s just simple flow, write and validate code together. We find it tough (we all do) because we have never done that before. And it’s always tough to do things that we haven’t done before. It’s natural.
TDD(Test driven development) is really powerful programming practice. Doing TDD is very smooth process if testing framework is already setup. It’s just following some easy steps.
1. Write test scenarios as empty test cases.
2. Pickup one scenario/test case and write a simple case.
3. Make the case pass.
4. Add another case for current scenario if any. and repeat from 3 until all cases for current scenario are finished.
5. Pick another scenario/test case.
I have curated these points from my experience, it’s no hard and fast rule to follow them. These are there to just help you get started.
The point of TDD is that it forces you to have a direction in mind before you start charging into the fray, and having a direction in mind leads to better designs.
Writing empty test cases are very crucial, might seem childish thing to do but think of them like scenarios/sub-tasks that we write on
feature documentation as a check list. We complete a sub-task and check it and move on to another. Writing empty/failing test cases work same way. You have to write all of your scenarios first as empty/failing test cases(try to cover all scenario, it’s ok if you can’t get it all, add them later as you find).
Manual testing is often quick and easy and satisfying — you can directly test your application, one can see the results immediately on your screen, and one can interact with the application “for real”, instead of in the sometimes-awkward scripted/mocked mode of unit tests. It’s a very natural instinct.
However, it’s also largely-wasted effort! A manual test only verifies the current state of the code base. As soon as you make a change, you’ve started to invalidate the results. If, however, you take the effort to encode the test in code as an automated test, it continues to be valid indefinitely into the future.
More important advice is to always write regression tests. Encode every single bug you find as a test, to ensure that you’ll notice if you ever encounter it again.
This doesn’t have to just mean “bugs reported by a user” or anything similar; Any bug you find in the course of development is worth writing a test for. Even if you find yourself half way into typing a line of code, realise you’ve made a mistake, and backspace half the line: Pause, and ask yourself “If I had made that mistake, is there a test I could have written to demonstrate it?”
The goal is, essentially, to ensure a ratchet on the types of possible bugs in your system. There are probably infinite no. of bugs you could write, but the set of desired behaviours of your system is relatively finite, and the set of bugs you’re likely to write is similarly limited, and if you turn every bug into a test on one of those behaviours, you’ll eventually have converge on testing for most of the important failure modes of your system.
- Makes the Process Agile
- Quality of the Code
- Find bugs early
- Provides in code documentation
- Confident changes and Integration
- Reduces cost
At the different stages of the cycle you are solving different problems, so the aesthetics change: Write a test-what should the API be? Make it compile — do as little as possible to satisfy the compiler. Make it run — get back to green so you have confidence. Refactor — remove duplication to prepare for the next test.
In TDD, we write only a few lines of code each time to pass a failing unit test. So, our development cycle is very very short, which makes debugging very easy too. How easy would it be to debug, if we could know that our code was just fine a few seconds back?
Developers love to learn about software by reading code. Even when we work with any 3rd party library, we tend to move directly to the code examples. Unit tests do the same for your production code. It makes other developers’ lives easier by being able to understand how your feature should work.
Points to remember:
- Writing tests after writing the code is hard. As the code can be written without testability in mind, it might not be testable, or properly decoupled/modularized. Hence, we would need to change our code again to make it testable.
- Writing tests subsequently after writing code does not bode well, since we already know that our code works. Writing tests before the code can be fun, as the test at first fails, and then we write code to make it pass.
- When we write tests after the code, it leaves holes in the test suite, as we are highly likely to miss some corner cases here and there. When we run a test suite full of holes, passing all the tests means nothing at all.
One of the primary reasons for doing TDD is that it makes refactoring a piece of cake. Most of the time we avoid fixing bad code in our project as it might break the system. With TDD, we can be brave about refactoring as we know that the consequences of our changes are just a “run all tests” away.
In this section there are couple of answer to some common questions and misconceptions about Test Drive Development from this blog.
- T.D.D. requires much more time than “normal” programming!
What actually requires a lot of time is learning/mastering TDD as well as understanding how to set up and use a testing environment. When you are familiar with the testing tools and the TDD technique, it actually doesn’t require more time. On the contrary, it helps keep a project as simple as possible and thus saves time.
- How many test do I have to write?
The minimum amount that lets you write all the production code. The minimum amount, because every test slows down refactoring (when you change production code, you have to fix all the failing tests). On the other hand, refactoring is much simpler and safer on code under tests.
- With Test Driven Development I don’t need to spend time on analysis and on designing the architecture.
This cannot be more false. If what you are going to implement is not well-designed, at a certain point you will think “Ouch! I didn’t consider…”. And this means that you will have to delete production and test code. It is true that TDD helps with the “Just enough, just in time” recommendation of agile techniques, but it is definitely not a substitution for the analysis/design phase.
- I am able to write code with very a few bugs, I don’t need testing.
You may able to to that, but is the same consideration valid for all your team members? They will eventually modify your code and break it. It would be nice if you wrote tests so that a bug can be spotted immediately and not in production.
That’s it. Thanks a lot for giving your time.
This is my first step for spreading awareness towards TDD. I want to extend it to help fellow devs with tech side implementation. I want to call for volunteers who could help me with that. I can manage for Android, looking for iOS, JS, PHP, Python, Java or whichever tech you love. You can reach me out on twitter or linkedIn. Be the change you want to see in the world.
I have taken some tricky points from some of the following articles. If you want to read more, please go ahead and read.
Any questions and suggestions are welcome.
The Outrageous Cost of Skipping TDD & Code Reviews
Update Jan 26, 2019: It has been brought to my attention that the claim “fixing a production bug may cost 100x more…
How I Write Tests
The longer I spend as a software engineer, the more obsessive I get about testing. I fully subscribe to the definition…
The mindset behind Test Driven Development (TDD)
TDD, or Test Driven Development in full, is not a new concept in modern software development practices. However, many…
5 Common Misconceptions About TDD & Unit Tests
Most developers seem to agree that testing is good, but developers frequently disagree about how to test. In this…
Mocking is a Code Smell
Note: This is part of the “Composing Software” series (now a book!) on learning functional programming and…
Test Driven Development: what it is, and what it is not.
Test driven development has become popular over the last few years. Many programmers have tried this technique, failed…
Does TDD really lead to good design?
Recently I tweeted that TDD can't lead to a good design if we don't know what good design looks like. I was also saying…