A rounding helper for splitting a bill, with a thorough-looking example table.
@ParameterizedTest
@CsvSource({"1000,2,500", "1000,4,250", "900,3,300", "100,1,100", "50,2,25"})
void splitsEvenly(int total, int ways, int each) {
assertEquals(each, Money.split(total, ways).get(0));
}
@Property
void splitAlwaysPreservesTheTotal(@ForAll @IntRange(min = 0, max = 1_000_000) int total,
@ForAll @IntRange(min = 1, max = 50) int ways) {
assertEquals(total, Money.split(total, ways).stream().mapToInt(i -> i).sum());
}
Property failed after 14 tries, shrunk to the minimal case:
total = 1, ways = 2
expected 1 but was 0 // split returned [0, 0]
Every example in the table divides exactly, which is the bias to notice: a human
choosing cases picks numbers that make the arithmetic easy to state, and those
are exactly the cases where integer division has nothing to go wrong with. Ten
pence split three ways is where the money disappears.
The property is also a better specification than the table. "The parts sum to the
total" is the actual business rule — a bill split must not lose or invent money —
and stating it that way makes the missing requirement obvious: somebody must
receive the remainder pennies, and the code must decide who.
Shrinking is what makes the technique usable. The first failure the generator
found was probably some large unhelpful pair; the report gives you 1 and 2,
which is a case you can reason about and paste straight into a regression test.
The limit is worth stating too. Properties need an invariant, and a lot of code
has none beyond "does what the specification says", where examples remain the
right tool. The strongest pattern is both: properties for the invariants,
examples for the specific agreed cases including the one just found.