It is surprising how many times I still find myself talking to software teams about unit testing. I’ve written before that the term “unit testing” is not definitive. “Unit testing” simply means that tests are being defined that run at the unit level of the code (typically methods or functions). However, the term doesn’t mean that the tests are meaningful, valuable, or quality-focused.
From what I have seen, the term is often used as a synonym for path or branch level unit testing. Although these are good places to start, such tests do not form a complete unit test suite. I argue that the pursuit of 100% path or branch coverage and the exclusion of other types of unit testing is a waste of time. It is better for the overall quality of the code if the unit tests achieve 80% branch coverage and include an effective mix of other unit test types, such as domain, fuzz and security tests.
For the moment I’m going to focus on domain testing. I think this is an area ripe for improvement. Extending the “ripe” metaphor, I’d say there is significant low-hanging fruit available to development teams which will allow them to quickly experience the benefits of domain testing.
First, for my purposes in this article what is unit-level domain testing? Unit-level domain testing is the exercising of program code units (methods, functions) using well-chosen values based on the sets of values grouped, often, by Boolean tests in the code. (Note that the well-chosen values are not completely random. As we will see, they are constrained by the decision points and logic in the code.)
The provided definition is not meant to be mathematically precise or even receive a passing grade on a comp-sci exam. In future postings I’ll delve into more of the official theory and terminology. For now I’m focused on the basic purpose and value of domain testing.
I do need to state an assumption and create two baseline definitions in order to proceed:
Assumption: We are dealing only with integer numeric values and simple Boolean tests involving a variable and a constant.
Definitions:
- Domain - the set of values included (or excluded) by a Boolean test. For example, the test, “X > 3” has its domain of matching values 4, 5, 6, … and its domain of non-matching values 3, 2, 1, …
- Boundary – The constant used in a Boolean test forming the point between the included and excluded sets of values. So for “X > 3” the boundary value is 3.
Now let’s look at some code. Here is a simple Java method:
public int add(int op1, int op2) {
return op1 + op2;
}
This involves one domain, the domain of all integers, sort of. Looking closely there is an issue; the domain of possible inputs (integers) is not necessarily the domain of possible (correct) outputs.
If two large integers were added together they could produce a value longer than a Java 32-bit integer. So the output domain is the set of values that can be derived by adding any two integers. In Java we have the constants MIN_VALUE and MAX_VALUE in the java.lang.Integer class. Using that vernacular, the domain of all output values for this method can be represented as: MIN_VALUE – MIN_VALUE through MAX_VALUE + MAX_VALUE.
Here is another simple method:
public int divide(int dividend, int divisor) {
return dividend / divisor;
}
Again we seem to have one domain, the set of all integers. However we all know there is a problem latent in this code. Would path testing effectively find it?
(more…)