Avoiding Test-Case Permutation Blowout
stevenhicks.me
stevenhicks.me
The test then becomes something like
Check.Sample(
Gen.Bool, Gen.Bool, Gen.Bool
(loggedIn, hasGreenHair, likesBananas) => {
var anyTrue = loggedIn | hasGreenHair | likesBananas;//the property to test
var messagePrinted = formatMessage(loggedIn, hasGreenHair, likesBananas);
Assert.Equal(anyTrue, messagePrinted);
}
);
This is obviously overkill for a test space of cardinality 8. But for complex cases this can provide better coverage of unexpected interactions than just stabbing a few positive and negative results.Another approach for this test would be to use a metamorphic property (also possible with CsCheck). You basically come up with a rule that says something like, "if a user is logged in, then it should not matter if they have green hair or if they are like bananas". Then just let the framework search for falsifiable cases.
A real-world example of this was when I designed a scheduling system that would compile a set of abstract rules down to concrete events. I used metamorphic testing to look for cases where "If I add a new rule to a calendar, the total observable complexity of the calendar should be greater than or equal to before" (the equal case is if the new rule was equivalent to an existing rule. This allowed me to ensure that the internal complexity wasn't dropping observable details.
Does this do what I think it does?
This is terrible practice. Tests should not replicate the business logic of the production code.
If you use literal expected results for all of your test cases, you're probably either not covering enough cases or making your tests so verbose and long that they will start to diverge in weird ways.
They made a tool called ACTS which allows you to specify some variables which affect your testing, then generate a bunch of tests with combinations of just n variables at a time where typically n is 2 or 3 but can be higher, perhaps if you're automating the tests or have exceptional patience.
They have some empirical data on how the chance of finding additional bugs decreases as you increase n.
More info here or by searching for NIST ACTS:
https://csrc.nist.rip/groups/SNS/acts/documents/comparison-r...
In the former, truth-table testing doesn't really add much value, and in the latter, I'd reach for hypothesis testing/fuzzing.
That said, writing exhaustive truth-table test doesn't look so bad with table-driven tests, like:
tests = [
#logged_in, green_hair, likes_bananas, msg_shown
(true, true, true, true),
(true, true, false, false),
...
]
for t in tests:
assert(foo(t.0, t.1, t.2) == t.3)These tend to be not so great at documenting intent and usage, though, which is the primary goal of your tests. Generally, I'd reach for both – offering a limited number of clearly defined tests to demonstrate common usage to future readers, while letting the fuzzing, etc. sort out the rest.
Just like many other industries. I recently learned about inspectors who sample cacao beans at the warehouse for the many ways they can go wrong before being processed.
As someone who really appreciates strong examples to motivate learning generalised approaches to work, a variation of this article could be a great motivation to introduce people to parameter based testing, or (closely related) fuzzing.
If all variables are independent, then the number of test cases is the max of the number of (relevant) values for each variable (i.e., you can combine cases).
When a variable depends on another, you have combine them. But even then you can quantize the outcomes (i.e., which combinations result in different behaviors) and then partition the values to produce those combinations.
If you don't know if variables are dependent, you have to do some stochastic testing and find associations to investigate for causality, distinguishing confounders, etc. -- assuming that's cheaper than just running a full gamut of combinations.
Sometimes you can use grey-box indicators from the system itself to quantize behaviors, or define coverage as traversal through such indicated system states.
So, yeah: great start, but more to it...
This reads like someone rationalising the slow loss of their religious faith
First, he speaks of permutations when he means combinations, and assumes that all variables are binary. Then he decides that eight test cases is “getting ridiculous.” Huh? It depends on how they are performed. No number is ridiculous a priori.
What he should be doing is thinking through the technical risk and his coverage goals. It may be that all pairs coverage is worth doing, or random testing, or exhaustive testing. Or testing based on sensitivity analysis. Or testing based on a decision table.
I don’t have a problem with simple heuristics, but the author does nothing to justify his personal preference.