Writing combinatorial tests usually requires that the author of the test plans for combinatorial testing upfront. This requires that all the combination variables and their values are identified and combined with the code that creates possible combinations and loops through each one of them. However, it would be nice to simplify the process and enable testers to write combinatorial tests in a manner that is as close as possible to a test that checks just one combination. This is where combinatorial Sketch‘es come in. A combinatorial sketch allows a tester to write combinatorial tests in an intuitive way without worrying about variable identification, calculation of combinations, and any explicit loops.
1 |
|
Due to the problem with the combinatorial explosion, a practical application of combinatorial testing requires narrowing down the number of combinations that each test can explore. Combinatorial testing is often even put aside altogether, as testers think that the number of combinations is either very large or close to infinite and therefore nothing can be done, and they only write test cases that only check one or a few of the combinations.
The addition function
Let’s take a simple toy example, the add(a, b)
function defined below, and see how we can write
different tests for it to make sure it works as expected.
1 | def add(a, b): |
Typical check
A typical test for checking the behavior of the add(a, b)
function could look something like this:
1 |
|
Where the check_add
is a parameterized test that calls the add(a,b)
function and asserts the validity of the result.
Basic test
Basic testing of the add(a, b)
function could then use a feature
that calls the check_add
test with different values for a
and b
of the tester’s choosing.
For example,
1 |
|
More advanced test
Of course, a more advanced tester could also move all the cases into something like a list of tuples data structure, and use a for-loop to check each case. Maybe something like this:
1 |
|
This version looks ok and seems like it would allow you to easily add more explicit cases as needed. However,
the two versions are conceptually the same. We are only checking six combinations
of a
and b
values. We could add more cases, but the list would become pretty
long. Is there a better way?
Combinatorial sketches to the rescue!
Before sketching any combinations, let’s remember that the most basic test that checks just one combination looks like this:
1 |
|
It has no for-loops; it is simple and sweet. Actually, if you compare the basic and more advanced versions of the test, one might argue that the basic version is much cleaner than the second. So let’s take the simplest test above that checks only one combination and see how we can improve it with combinatorial methods.
First, it is evident from both tests that the initial set of interesting values for the a
and b
parameters is the following:
1 | values = {0, 1, math.inf, math.nan, 1/3, 2**-200, 2**200} |
Second, we note that any possible value in the set above could either be positive or negative. This can be easily expressed as multiplying a value by either a positive or negative one. This gives us another set:
1 | sign = {1, -1} |
Having the sets above defined, we can express a high-level test logic as follows:
- Let
a
be either of the possible values in the set of multiplied by either of the values - Let
b
be either of the possible values in the set of multiplied by either of the values - Call the
check_add
with the chosen values ofa
andb
Reading the three steps above, some might object that we are incorrectly using the word either
.
However, in modern English, the rules about using the word either
are not as strict as they used to be
and either
could be used for more than two choices. But for some of you, it might sound weird.
Here is a quote:
In modern English, it is not unacceptable to use “either” if you are given a choice between more than two items.
Let’s sketch it out
Having accepted the fact that in the modern world we could use the word either
to choose from more than two values
we could translate the three-step test procedure above into a combinatorial Sketch
supported by as follows:
1 |
|
Here is the same code, but with comments:
1 |
|
Let’s put together a complete test program that we can run.
1 | import math |
When executed, the program will produce the following output:
1 | Oct 12,2023 20:56:21 ⟥ Scenario test add, flags:TE |
The six lines of test code all of a sudden converted into the execution of combinations for the add(a, b)
function under test!
We actually covered all the possibilities defined by the values
and sign
sets.
To emphasize, by removing the supporting code, all the magic to create combinations was done by in these six lines:
1 | values = {0, 1, math.inf, math.nan, 1 / 3, 2**-200, 2**200} |
That is one sweet combinatorial test that takes testing to a whole new level and allows new test engineers to start seeing the true power of having combinatorial testing at their fingertips.
If you read the code carefully, you will notice that it is very intuitive, and the either() function defines the possibilities, so we don’t have to worry about any for-loops or calculations of all the combinations. All the magic is done by !
Wrapping up
With test Sketches, sketching out combinatorial tests becomes very easy. The use of the either() function introduces the possibilities into the test code that otherwise would just check only one combination. While the same functionality can be achieved using a standard combinatorial test code that uses either nested for-loops or computes all combinations using a Cartesian product function, the ease and fun of using test Sketches is unbeatable. Sketches allow us to express choices in a very natural way, and in more complicated test procedures, they are hard to match in terms of test code expression and readability.
If you want to learn more, I invite you to read the Combinatorial Tests section in the Handbook to get a glimpse of how can make your testing flow. Be sure to take combinatorial Sketches for a spin. You will have fun!