Unit testing often involves a lot of tedious, repetitive work that takes a lot of your time that could be spent on something more meaningful and fun (like adding actual functionality to your code).
On the other side, the value of writing unit tests is often hard to grasp. As a good developer, you (obviously) don’t write bugs, so why test at all?
However, before you check in your code changes, you still want to have a good feeling of not having crashed the whole system (you also don’t want to find out that you actually did after pushing). Put still, you have to find the sweet spot where effort for unit testing pays off as much as possible.
So the one rule to be better at unit testing is:
Be more efficient!
Efficiency means that you want to increase your outputs while decreasing your inputs.
Applied to better unit testing, this means that you want to
1. maximize the benefit of your unit tests, while
2. minimizing the effort for writing, executing and maintaining your tests.
This sounds very generic, so far. So let’s dig into those two sides, and see what you can do to address them in order to do better unit testing.
1. Gain as much value as possible from each unit test
In my experience, there are three aspects of unit tests that are usually neglected. By using them in your projects, you can 10x the value of every single unit test your write.
Maximize learnings from test execution
Most people think that the sole purpose of running a test is to find bugs in their implementation. However, there are various things that you can learn about your system just from executing your test cases. You can read more about this in the following post: “
5 things you can learn from executing your test cases”.
Use evaluation metrics on your test cases
By going beyond pass/fail of your unit tests, you can also increase the value of every single unit test to your project.
With metrics such as code coverage or mutation scores, you can learn how much of your system is already tested, and more importantly: how much bugs are potentially detected by each unit test.
With this information, you can optimize your testing strategy (where do you need more unit tests? Which tests can be ignored/deleted?) to maximize the gained value.
The various purposes of a single unit test
Besides running a test, and learning about the tested system from it, there are quite some other areas in which your test case provides value.
You can use it for documenting your code.
But you can also use it before writing any code to think about and specify what you actually want to do, and which conditions your system must satisfy (this is what TDD essentially does).
2. Minimize effort for (i) creation, (ii) execution, and (ii) maintenance of your unit tests
By doing unit testing wrong, most people create a lot of effort that is actually not needed. In the following, I summarize the top 4 actions you have to take to minimize your effort for unit testing.
Use the right methods
Defining the right unit tests can be very difficult.
However, there are two methods that help you save about 80 % of your overall effort when it comes to unit testing.
- With Equivalence Class Partitioning, you can define groups of input data that should trigger the same execution path within your system. This implies that it should be sufficient to implement only one test case for each equivalence class to cover the whole group of input data.
- And with Boundary Value Analysis, you can derive the most efficient test cases from your equivalence classes.
Generate tests
When requirements are specified to a sufficient extend, you can even generate your test cases automatically from this definition.
The golden rule here is that specifying requirements should (obviously) be less work than writing the code for the unit tests in the first place. Writing test cases, but with a different syntax, and then generating your NUnit/XUnit/MS-Test syntax from it, actually has no real value at all.
Again, Equivalence Class Partitioning is a perfect fit here.
Use a visual interface to manage your tests
Whether you generate your tests automatically from e.g. equivalence class definitions, or you choose to implement each test case by hand, you should rely on maximum tool support when it comes to managing your unit tests. With visual interfaces, modern testing tools allow maximum usability when it comes to navigating, understanding, and maintaining your test cases on the long run.
Automate test execution
Of course, writing and maintaining test cases takes most effort when it comes to unit testing. However, the execution of test cases is a part that also takes time that could be spent otherwise, and can be automated quite easily. By automatically triggering test execution before every code commits (as part of your Coninuous Integration Build), you (i) have to force yourself to always keep your unit tests up-to-date, and most importantly, (ii) you gain maximum insights into effects of your code changes, within a click of a button (I’m talking about the “commit”-button, which you would have clicked, anyways).
By setting up a regular test run that is triggered automatically at specific times (as part of your “Nightly Build”), you can automate this even further – no need to click anything, but have the test results executed on your current code version prepared for you when you turn on your laptop in the morning.
In this post, you learned about several particular techniques that you can use for maximizing your efficiency, in order to do better unit testing.
What is your opinion about these techniques?
Do you have particular experiences that you want to share?
Let me know in the comments section below!
Daniel Lehner