So far we’ve got a piece of code to compare unordered binary trees and another one to randomly generate strictly binary tree shapes. Now before we’ll put together a property based test around comparing unordered binary trees, we’d like to be sure that generation of trees works fine. In this context “fine” means that:
N
are generated for a given NC_n
possible shapes can be obtainedN
means number of internal nodes and only those will count toward weightC_n
means Nth Catalan number - wiki,OEISObviously this can also be tested in a property based approach. We’ll start with property 1 which is easiest to verify, but still gives minimum confidence in implementation correctness.
N
are generated for a given NThis would be the simple test where for every possible N
denoting size of a tree
a random tree would be generated, then Leaf nodes would be trimmed to allow for counting
and then number of nodes would be counted.
So first we’ll need a Trimmer
to remove leaves from a tree.
It’ll recursively traverse a tree and remove nodes that have neither left nor right
subtree.
Start with boilerplate code of right interfaces for that:
Trimmer.java
public class Trimmer<K> implements UnaryOperator<Tree<K>> {
@Override
public Tree<K> apply(Tree<K> tree) {
return null;
}
}
Then inside apply start with handling edge cases on which recursion has to stop, followed by recursive step.
Trimmer::apply
@Override
public Tree<K> apply(Tree<K> tree) {
if (tree == null) {
return null;
}
if (tree.left() == null && tree.right() == null) {
return null;
}
tree.left(apply(tree.left()));
tree.right(apply(tree.right()));
return tree;
}
Now the leaves are trimmed remaining nodes in a Tree
have to be counted,
again approaching this in a recursive manner is easiest to implement.
TreeSize.java
public class TreeSize implements ToIntFunction<Tree<?>>, Function<Tree<?>, Integer> {
@Override
public int applyAsInt(Tree<?> value) {
if (value == null) {
return 0;
}
return applyAsInt(value.left()) + applyAsInt(value.right()) + 1;
}
@Override
public Integer apply(Tree<?> tree) {
return applyAsInt(tree);
}
}
Now putting this all together into a test case will look the following. Start with initializing required test dependencies:
Test Setup
class RandomTreeRemyTest {
private final Trimmer<Integer> trimmer = new Trimmer<>();
private final TreeSize treeSize = new TreeSize();
private final Random random = new Random();
private final RandomTreeRemy<Integer> generator = new RandomTreeRemy<>(random);
}
And finally test case itself, utilizing Java functional APIs methods for concise syntax. JQwik defaults to 1000 runs per property, so having upper cap at 65k is just chosen so that test runs reasonably fast on modern machine.
treeGeneratedWithExpectedSize
@Property
void treeGeneratedWithExpectedSize(@ForAll @IntRange(min = 0, max = 65536) int size) {
Integer actual = generator.andThen(trimmer).andThen(treeSize).apply(size);
assertThat(actual).isEqualTo(size);
}