Unit tests, do they worth it?
Often I hear people doubting the effectiveness of unit tests, they usually say “most bugs we encounter with are ‘integration bugs'”, “they usually happen due to integration flaws with the DB, with some external API, with some configuration, etc.”
Do they right? Well, it depends.
Consider the following scenario in which I am sure you’ve been many times before: you’ve been asked to make a change in the system, let’s say to add a small feature. Unfortunately, the area in the code in which you are about to make your changes does not perfectly fit the new requirements. You have two options here:
- To redesign/refactor the code so that it will fit the new requirements
- To implement the requirements without refactoring, even at the cost of making the code uglier.
Option number 1 is the preferred one, but it’s dangerous, thus, teams who don’t have unit tests that cover their code, usually avoid this option and immediately turn on the easier option (but of course the bad one) which is option number 2.
If you want to have a long-lasting software, you need to make more significant changes in your code (cleanups and redesigns) and you should do it more frequently.
A case I had a few days ago.
A few days ago I had to do some refactoring operations. Not something huge, I just had to break a class with 2 responsibilities into 2 smaller classes with a single responsibility each. This means that I had to move lines from one place to another, I had to split methods into pieces, I had to change the constructor’s signature, I had to change the order of some lines, etc. You probably understand how dangerous this is – something must go wrong, and something did go wrong. 3 times. This means 3 bugs!
Luckily I covered this class with unit tests just before I started tearing it up, and those tests saved my a***s 3 times. They prevented 3 bugs.
Ok, so why not manual or integration tests?
A huge advantage of unit tests over manual or integration tests, especially when you are refactoring code, is that unit tests are very fast (if you followed the rules), and that’s crucial for refactoring – you better progress in small steps and test your code after each step. That way, if you break something, you undo your changes to the last known working state of the code very quickly and without diving into debugging.
Do you remember this refactoring operation I told you about? Since running the relevant tests took me exactly 2 seconds, I literally run the unit tests every few minutes, and when I did break something, I haven’t even had to debug to find out what caused it, I simply undo the code back to a state in which I know it worked a few minutes ago and started again with much more attention this time.
Think about how it would have looked like if I had to test it manually – I would have had to run the whole application every few minutes, login to it, do 3-5 steps before I get to the screen where I can test my changes and then start testing it – very time consuming. If that was the case, I guess I would have given up on running it every few minutes and instead, I would have run it for bulks of changes every half an hour or so, but that would have led me to spend much more time on debugging whenever something went wrong.
Automated integration tests are usually a lot faster than manual, and yet, even they take too long to run (and I am talking about running only the tests that are covering the area I was working on), long enough to prevent me from runnin them as often.
A much better protection with Unit Tests
2 out of the 3 bugs I told you about earlier, were very thoughtful – they caused an exception to be thrown. These are very simple bugs to find either with manual or integration tests. The third bug was an evil one, it did not throw any exception, instead, it corrupted the data produced by class, and it did it without any warnings. I call this kind of bugs – “silent killers”. Would I have found it with manual or integration tests? I doubt it! Usually, this kind of tests focus on the “happy path” looking for major failures or exceptions being thrown.
Luckily, since my unit tests are very specific and are covering every behavior of the unit, they managed to catch this vicious bug.
So, do they worth it? In my opinion – hell yeah!
If you don’t have unit tests in place, you will almost always choose the easiest option – driven by fear, you will avoid the needed redesign, and most likely go for the quick and dirty solution.
Unit tests are there to help you be more aggressive with your code when needed. They are there to help you do more frequent and more significant changes in your code, because if you don’t, your code will eventually be a mess.