Property based testing

I initially read about the Python framework Hypothesis and I must say I like the overall idea to just define ranges and the framework does a dozen of tests with semi-random values.

This might lead to combinations, you normally wouldn’t think of.

So since there is a java version (jqwik) of it, let us look into the various generators that can be used.

First steps

In general, the whole handling of pretty straight forward.

@PropertyDefaults(tries = 10) (1)
public class TodoTest {

    @Property
    public void testCreateTodo(@ForAll String anyStr) { (2)
        Todo todo = new Todo();

        todo.setTitle(anyStr); (3)
        todo.setDescription(anyStr);

        assertThat(todo.getTitle()).isNotNull(); (4)
        assertThat(todo.getDescription()).isNotNull();
    }
}
1 This tells jqwik how often to run the test with different generator values.
2 Here @ForAll just marks the value that is taken from a generator.
3 The parameter anyStr can now be used like you’d expect.
4 And finally the tests can be done with your favorite assertion library.

Adding a bit of complexity

@PropertyDefaults(tries = 10)
public class TodoTest {
    private static final int FUTURE_TIME = 1741598467;

    @Property
    public void testCreateTodoWithDate(@ForAll String anyStr,
                                       @ForAll @IntRange(min = TodoTest.FUTURE_TIME) int unixtime) (1)
    {
        Todo todo = new Todo();

        todo.setTitle(anyStr);
        todo.setDescription(anyStr);

        DueDate dueDate = new DueDate();

        dueDate.setStart(LocalDate.now());
        dueDate.setDue(Instant.ofEpochMilli(unixtime * 1000L) (2)
                .atZone(ZoneId.systemDefault()).toLocalDate());

        todo.setDueDate(dueDate);

        /* Arbitrary and contrived test */
        Condition<Todo> cond1 = new Condition<>(t -> (3)
                t.getDueDate().getStart().isBefore(t.getDueDate().getDue()),
                "Start date is before due date");
        Condition<Todo> cond2 = new Condition<>(TodoBase::getDone,
                "Todo must not be done");

        assertThat(todo).is(allOf(cond1, cond2));
    }
}
1 This is pretty much the same, besides it includes an @IntRange now.
2 Here we use unixtime as an input to create dates.
3 A small test of test combinators..

Conclusion

During my tests I’ve found some interesting bugs, mostly related to empty string and some weird combination of unicode characters. I think the handling is pretty easy and generators can be a huge help to avoid some kind of errors.

As usual, my showcase can be found here: