There are numerous online articles listing the usual, generally accepted reasons for why unit tests should not be neglected. But even though incredibly useful, unit tests are still often ignored among developers. Most of them would probably agree on their benefits but would argue that they still take too much time and effort which could otherwise be used on developing new features. In this article, we will present the three main reasons we try to unit test as much of our code as possible.
They speed up regression testing
Code evolves. Whether you are adding new features or changing existing ones, most of the code you once wrote will be changed sooner or later. Without unit tests, every code change would require arduous manual testing.
Let us say that you are working on a backend for a mobile app and you would like to refactor some of the code. Maybe you want to group some of the functions in a class or break them up in a couple of smaller functions. Since your code is not covered with unit tests, the only way to be sure your change has not broken anything is to test it manually.
So you start up the app, login, go to the necessary screen, test various inputs, then you log in with a different privileged user, go to the necessary screen again, test the various inputs again, then you log in with a user originating from a different country, go to the necessary screen yet again, test various input yet again and…you find a bug.
You fix the bug and in the process, you refactor some more since the new logic introduced new refactoring possibilities. But now you are not sure that the latest refactoring did not break anything.
You can not simply test the case which failed the last time, you also have to re-test all of the cases before it. So you start up the app again, log in, go to the necessary screen again and immediately find a new bug. Then you give up, check out your changes, delete the refactoring branch, and move onto a new ticket.
There is a saying that goes something like:
A programmer should either do it only once or automate it.
That philosophy is especially important when it comes to testing. If programmers have to repeat manual testing steps every time they change a piece of code, they will either waste a lot of their time which could have been spent writing new features and unit tests or worse, become irritated and start pushing untested code, hoping that the QA team will catch any bugs they may have introduced.
They enable you to test scenarios which are hard to reproduce
There are some scenarios that you would like to test manually, but it requires a lot of steps to reproduce. For example, you discovered that when a user who: has an IP address originating from somewhere in Brazil has more than 20 orders in the last two weeks has Android version 8.1
tries to access a specific URL, an exception is raised.
To test this without unit tests, you would have to use something like a VPN to spoof your IP address, manually add more than 20 order records to your database and do it all from a phone running Android version 8.1. But with the power of unit test mocking, you could just rig your code to pretend as if it meets all of these conditions. Python-like pseudocode for this example would be:
In just three lines of code, you are ready to reproduce the bug and start working on the fix. Most of the software issues you will meet while developing your product could probably be reproduced and tested with unit tests.
They can serve as documentation for other developers
Unlike the first two points, which we believe most of the developers writing unit tests are going to be familiar with, this one surprised us as to how effective it is.
Working on someone else’s code can be a daunting task. New developers may not be familiar with the business logic, or they may see some functions for the first time and the coding style might be completely different from the one they are used to.
In our experience, going through unit tests can bridge the initial gap of understanding what the code is trying to convey. When you see a unit test named: test_user_attending_event_receives_email() you know that a part of the code is responsible for recording when a user attends an event and for notifying them with an email.
Going further you could also discover what some code branches are used for. Maybe you will see a test: test_user_attending_finished_event_throws_exception()
so you make a mental note that attending an event that already finished will cause an exception. That way you are piecing the puzzle and slowly understanding the bigger picture.
Not only will the unit tests, in this case, shield the code from developer’s mistakes, they will also serve as a high-level overview, or maybe even full-fledged documentation of the code they are working with.
Even though none of these reasons are unheard of, we chose them since they are something we are faced with almost on a daily basis. We do not shy away from trying out new things or refactoring our code because we know that it takes us merely minutes to test it.
Whenever we are about to modify a part of the code which is not unit tested, we see that as much of an important issue as the code changes themselves.
There are of course exceptions to all of this, situations where incorporating unit tests is not feasible. But for the most part, we would advise looking at unit tests not as time-wasters, but as a handy tool which could save you time and nerves in the long run.