As seen in the introduction a property usually starts with a forAll
and contains some, usually boolean, condition.
To express some more complex properties we'll take a look at all the combinators that propCheck offers.
At it's core a Property
is just a wrapped generator of test results, as such it can be created from every type that has an instance of Testable<A>
, which at the time of writing this are: Boolean
, TestResult
and Property
.
Most combinators include overloads for Boolean
so that instead of writing combinator(Boolean.testable().run { true.property() })
one can just write combinator(true)
.
Run a property test given some parameters from forAll
. It has a few overloads:
forAll(arb: Arbitrary<A>, ..., f: (A) -> Property)
delegates to forAllShrink
with the generator and shrinker provided by Arbitrary<B>
.
It can also lookup a default instance for Arbitrary (only for basic datatypes), so that all you have to specify is the type.
propCheck {
forAll { i: Int ->
i < 0
}
}
// prints =>
*** Failed! (after 1 test):
Falsifiable
0
forAll(gen: Gen<A>, ..., f(A) -> Property)
forAll with an explicit generator, does no shrinking.
propCheck {
forAll(arbitrarySizedInt(), Boolean.testable()) { i ->
i < 0
}
}
// prints =>
*** Failed! (after 1 test):
Falsifiable
0
Usually the ide is good enough at choosing the right method, so it's best to just start typing and not worrying about it.
forAll
can also safely be nested to avoid having to cram everything into Tuple's.
forAll
but with an added parameter (B) -> Sequence<B>
that shrinks failing cases.
propCheck {
forAllShrink(arbitrarySizedInt(), ::shrinkInt, Boolean.testable()) { i ->
i < 10
}
}
// prints =>
*** Failed! (after 21 tests and 1 shrink):
Falsifiable
10
forAll
but does not output the parameters on failure.
propCheck {
forAllBlind { i: Int ->
i < 10
}
}
// prints =>
*** Failed! (after 16 tests and 1 shrink):
Falsifiable
forAll
but with an explicit show function (B) -> String
to show the parameters. (Usually defaults to Show.any()
which uses toString()
in the other forAll
methods)
propCheck {
forAllShrinkShow(arbitrarySizedInt(), ::shrinkInt, { "i: $it" }, Boolean.testable()) { i ->
i < 10
}
}
// prints =>
*** Failed! (after 17 tests and 3 shrinks):
Falsifiable
i: 10
forAllShrink
but without printing the parameters.
Compare two values for equality, adding a nice printout on failure.
Can be supplied with custom Eq<A>
but has a default to use == from kotlin.
propCheck {
forAll { (i, j): Pair<Int, Int> ->
(i - j).eqv(j - i)
}
}
// prints =>
*** Failed! (after 3 tests and 2 shrinks):
Falsifiable
(0,1)
Expected: -1 to be equal to:
: 1
Combine two Properties
failing if one or both fail. Also includes overloads with Eval<Property>
and () -> Property
to short circuit evaluation if failure is guaranteed.
Combine two Properties
failing only if both fail. Also includes lazy overloads like and
to short circuit if success is guaranteed.
Add a custom error text that is displayed on failure. The text is lazy, so even if you have a data-type that is expensive to print, you should be fine since this is only called on failure. (Including shrink failures)
propCheck {
forAll { (i, j): Pair<Int, Int> ->
counterexample(
{ "Custom descriptive text" },
(i + j == j + i)
)
}
}
Invert the property. There are some exceptions to using this together with and
/or
, so just make sure to keep this top-level.
Stop testing after this test.
Continue testing after this test.
once
and again
are mostly implementation details of forAll
, the interaction of these used together can be a bit weird.
More output when running tests. Usually prints intermediate results for each test run.
Same as verbose
but even prints while testing shrunk cases.
Testdata generated with forAll
is just a sample of the set of possible values. This directly relates the quality of the test to the quality of the generator used in forAll
.
propCheck contains some methods of statistical analysis to better undestand how inputs are distributed and to fail if that distribution is not satisfying.
Attach a specific label to a property.
propCheck {
forAll { i: Int ->
label(
if (i.rem(2) == 0) "evens"
else "odds", // label
i + i == 2 * i // test case
)
}
}
// prints something like this =>
+++ OK, passed 100 tests:
46,00% odds
54,00% evens
In this case
classify
will be easier to use.
Attach a label to a property if it passes the supplied condition.
propCheck {
forAll { i: Int ->
classify(
i == 0, // condition
"zero", // label
classify(
i != 0, // condition
"non zero", // label
i + i == 2 * i // test
)
)
}
}
// prints something like this =>
+++ OK, passed 100 tests (97,00% non zero. 3,00% zero).
Like label, but automatically creates the label from the supplied data. Useful for debugging when you want to see all values generated.
propCheck {
forAll { (i): NonNegative<Int> ->
collect(
i.rem(2),
i >= 0
)
}
}
// prints =>
+++ OK, passed 100 tests:
56,00% 1
44,00% 0
Label test data with the ability to put the labels into different tables.
propCheck {
forAll { i: Int ->
tabulate(
"Data",
listOf(i.toString()),
i + i == 2 * i
)
}
}
// prints something like this =>
+++ OK, passed 100 tests.
Data (100 in total)
3,00% -2
2,00% 2
2,00% 9
...
Add a coverage check to the testcase, but does not fail the test on its own (only inside checkCoverage
TODO LINK). Simply adds output if the coverage is insufficient.
propCheck {
forAll { i: Int ->
checkCoverage(
cover(
20.0, // percentage required
i == 0, // condition
"zero", // label
i + i == 2 * i // test
)
)
}
}
// prints something like this =>
*** Failed! (after 400 tests):
Insufficient coverage
4,51% zero
Add a coverage check to specific a table of labels. For that table it works the same as cover
.
propCheck {
forAll { intArr: IntArray ->
tabulate(
"Data",
listOf(if (intArr.isNotEmpty()) "not empty" else "empty"),
coverTable(
"Data",
listOf("not empty" toT 90.0),
true
)
)
}
}
// prints =>
+++ OK, passed 200 tests.
Data (200 in total)
99,00% not empty
1,00% empty
The order should not be important here.
Adds failure if any coverage check inside the property fails. Can be given parameters to tweak behaviour slightly.
Discard a test-result. This test is neither a failure nor a success, but if too many cases get discarded propCheck will fail. This can also seriously mess up the input data, so check if the above methods to make sure the data is fine.
propCheck {
forAll { i: Int ->
discardIf(
i < 0, // discard values below 0
i > 10 // test
)
}
}
Sometimes it can be useful to execute some code after test cases fail.
The smallest primitve for that is callback
but higher-level wrapper methods make life easier.
Append a callback that is executed after every failed test case. For example counterexample is implemented using callbacks.
Execute code (with or without side-effects) after the final test-failure.
Execute code (with or without side-effects) after every test-failure.