Unit Testing: Simple But Effective
So far in my career, I have worked for companies with widely differing approaches to how they develop their software. This has helped me gain some exposure of both the good and the bad practices, and how they affect the reliability and maintainability of the product in question.
One thing I have repeatedly observed is this: Whenever I come across a team that spends a significant amount of time fixing bugs, the root cause is almost always that they prioritize fast delivery of features above software quality. It is no surprise that this leads to bugs, and everyone knows that bugs actually slow you down on your goals. The time you could have spent developing new features is now spent fixing bugs, and with every iteration the bugs get harder to fix because the state of your codebase keeps getting worse. Sometimes you have really bad days, when fixing one issue gives birth to several others.
The cost of this one simple mistake (i.e., prioritizing delivery above everything else) can be huge, including but not limited to,
- souring of relationship between the team/company and the stakeholders/clients,
- managers becoming aggressive towards the engineers because of the increased pressure, cultivating a toxic working environment as a result,
- stakeholders/clients losing trust in your team/company and taking the project away from you.
Balancing Delivery vs. Quality
I'm not suggesting here that fast delivery of features should not be a priority. But what needs to be established is how fast you can afford to be so that you can be confident about what you deliver?
My personal experience says that you needn't compromise too much on speed. All you need to do is to start small and make a simple change. Start writing unit tests.
Here's a quick refresher of what a unit test is:
“A unit test defines the behaviour of the smallest logically separable unit of code. When you run a unit test, it fails if the code doesn't behave as defined.”
It doesn't sound like much, right? But here's what you get as a result of writing unit tests:
- you start writing smaller functions/methods that are easier to write unit tests for,
- the code becomes easier to read and maintain because of this,
- unit tests serve as an excellent and always up-to-date documentation of your code,
- running unit tests before shipping a feature can tell you if you broke something.
The biggest advantage, of course, is that many bugs are caught before the code is even shipped. Unit testing will not completely eliminate bugs, but their frequency will become much lower if the tests are well written and properly cover all the paths that the code can follow.
When to Write Unit Tests
Our goal is still to deliver features as fast as we can. But aiming for 100% test coverage can also cause us to significantly slow down. We need to decide what is important to test. I follow a very simple rule: If the code contains any logic, it should be tested. Some examples of what should not be tested are data classes, models/entities, configurations and exception messages.
Your team should decide what should and should not be tested, but this rule can serve as a good start.
I highly encourage anyone struggling with an onslaught of bugs to explore unit testing. Go back to some of the recent bugs you fixed, and ask yourself, “Could this bug have been avoided if I had written unit tests?” If the answer is “yes” most of the times, then unit testing is definitely right for you.
Unit testing doesn't magically make bugs disappear, but you have to start somewhere, and this is the most reasonable way to start.
Cover photo by Markus Spiske