In this article, I show you two tremendous time savers that you can start using tomorrow to save 60 % of the effort you use for unit testing.
Most of our time in unit testing is spent on what I call time monkeys. These time monkeys are tasks that cost a lot of time, without actually creating a lot of output (i.e. code).
In the following, I describe the two most prominent time monkeys, and how to overcome them using (i) Equivalence Class Partitioning, and (ii) Boundary Value Analysis.
Time Monkey 1: Thinking about potential tests, and evaluating whether you have covered a sufficient range of test cases.
In my opinion, most of your time in unit testing is spent on thinking about (i) what we actually want to test, and (ii) how many test cases we have to write to sufficiently test it.
Writing the actual code is just the last mile of unit test creation (and can be automated with the right tools, anyways).
Tremendous Time Saver 1: Equivalence Class Partitioning (ECP)
Why does ECP save most of your time in unit testing?
When using equivalence class partitioning, you have a structured way to approach your unit tests.
Instead of spending a lot of time for defining test cases without any feedback, you (1) think of relevant aspects of your system, and then (2) test these aspects, one by one. The actual test case itself is then just putting together the pieces (no brain-wringing, at all).
Instead of spending even more time to figure out whether you have already created enough unit tests, ECP allows you to automatically assess the completeness of your test suite (by checking if each equivalence class is covered by at least one test case)!
Nice benefit: You can even automate this to automatically (i) generate test cases, or (ii) evaluate the completeness of your test cases!
What does ECP do?
- You identify areas (i.e. value ranges for your input ports) that should trigger the same execution path in your system under test. Each of these areas can be considered an equivalence class. By defining these equivalence classes, you chunk the overall search space of potential input values for your test cases into small partitions, where each element within one partition can be treated equally (because it triggers the same execution path).
- When creating test cases for a particular method, you can choose one representative from each equivalence class to create a “representative” test case that coveres the whole partition of potential test cases.
- By checking whether your existing test cases cover all defined equivalence classes, you can also analyze whether you already cover all relevant execution paths of your system under test.
Sounds easy and effective, right? It actually is!
But let’s look at an example:
Therefore, we use the following method:
We can derive the following three relevant equivalence classes for this method:
- EC 1 (invalid age): If age is a negative value, we want the system to throw an error
- EC 2 (not an adult): If age is between 0 and 18, we want the method to return false
- EC 3 (adult): If age is larger than 18, we want the system to return true
Time Monkey 2: Selecting the most efficient test cases.
Each test case must be (i) created (this involves defining it, as well as writing code), (ii) maintained (e.g. updated, hosted, versioned, …), as well as (iii) evaluated after each test run (did it fail? what is the source for its failure? …). All of these steps cost time (and therefore, money). That is why you want to keep the number of your test cases as low as possible. However, constantly reducing them is again a very time-consuming task. How to determine tests that are most efficient? Which unit tests can be deleted?
Tremendous Time Saver 2: Boundary Value Analysis (BVA).
Why does BVA save you most of your time for selecting the most efficient test cases?
Boundary value analysis is actually an extension of equivalence class partitioning (see tremendous time saver 1 above). BVA is simply the way of choosing the most efficient representatives from each equivalence class for your test cases. This gives you a predefined way to derive the most efficient set of test cases that covers all relevant execution paths of your system under test, without the need to (i) think much about it (just follow the steps – or even automate them) or (ii) reduce test cases afterwards!
Nice benefit: As soon as you have specified your equivalence classes, you can even automate boundary value analysis to automatically generate test cases and corresponding code for you!
What does BVA do?
- In equivalence class partitioning, you define equivalence classes that consist of input factors that trigger the same execution path in your system under test.
- With boundary value analysis, you create test cases from these equivalence classes by picking their boundary values. A boundary value in this case is the value on the boundary between two equivalence classes, e.g. the smallest/largest value of an equivalence class for integers. These values are the most efficient representatives, because they tend to detect the most potentials for bugs in your system.
Sounds easy and effective? Again – it simply is!
Don’t believe me yet? Let me show you one example:
In the example of the isAdult method, BVA would yield to the following input values of age in created test cases:
- Lower Bound of EC 1: age = -99
- Upper Bound of EC 1: age = -1
- Lower Bound of EC 2: age = 0
- Upper Bound of EC 2: age = 18
- Lower Bound of EC 3: age = 19
- Upper Bound of EC 3: age = 99
With these cases, you cover the values that lead to most errors in your tested method (e.g. using <= instead of <), simply by using intelligent representatives from your equivalence classes!
In this article, I’ve tried to briefly show you how you can easily save most of your time in unit testing.
What are your favorite hacks when it comes to unit testing?
If you have already tried out the two mentioned methods, or at least plan to, what is your experience?
Let’s discuss in the comments section below!