|
Hegel 0.7.4
Property-based testing for C++
|
Hegel is a property-based testing library for C++. Hegel is based on Hypothesis, using the libhegel engine.
This guide walks you through the basics of installing Hegel and writing your first tests.
Using CMake:
Hegel requires CMake 3.14 and, by default, a C++20 compiler. The build downloads a small prebuilt shared library (libhegel, Hegel's native engine) for your platform; no other tooling is required.
To consume Hegel from C++17, configure with -DHEGEL_REFLECTION=OFF. This drops the reflect-cpp dependency: you lose default_generator (type-directed derivation for structs), but every other generator and combinator still works. (The designated-initializer parameter API, e.g. integers<int>({.min_value = 0}), then relies on a GCC/Clang C++17 extension.)
You're now ready to write your first test. In a new file:
Now build and run the test. You should see that this test passes.
Let's look at what's happening in more detail. HEGEL_TEST defines a property test as an ordinary function, self_equality, and registers it with hegel::run_all_tests(), which runs every test defined this way in the binary. You can also invoke the function individually — from main() or from any test framework (a GoogleTest TEST body, for example). Running the test executes your test body many times (100, by default).
The body receives a TestCase, which provides a TestCase::draw() method for drawing different values. This test draws a random integer and checks that it should be equal to itself. The macro also names the test in Hegel's example database, so a failure found in one run is replayed first in the next.
Next, try a test that fails:
This test asserts that any integer is less than 50, which is obviously incorrect. Hegel will find a test case that makes this assertion fail, and then shrink it to find the smallest counterexample — in this case, n = 50.
To fix this test, you can constrain the integers you generate with the min_value and max_value parameters:
Run the test again. It should now pass.
Hegel provides a rich library of generators in the hegel::generators namespace that you can use out of the box. There are primitive generators, such as integers, floats, and text, and combinators that allow you to make generators out of other generators, such as vectors and tuples.
For example, you can use vectors to generate a vector of integers:
This test checks that appending an element to a random vector of integers should always increase its length.
You can also define custom generators. For example, say you have a Person struct that we want to generate:
Note that you can feed the results of a draw to subsequent calls. For example, say that you extend the Person struct to include a driving_license boolean field:
Hegel can also derive generators automatically for reflectable structs via default_generator. This uses reflect-cpp to inspect the struct's fields and pick an appropriate generator for each:
Call .override(...) on the returned generator to customize individual fields (see override).
Use TestCase::note to attach debug information:
Notes only appear when Hegel replays the minimal failing example.
By default Hegel runs 100 test cases. To override this, write a Settings initializer after the test name:
These settings are the test function's default argument; passing a Settings when invoking the test (self_equality({.test_cases = 5})) replaces them for that run.
HEGEL_TEST is a macro around hegel::test(), which remains available when a macro doesn't fit — for example to run a lambda or another callable built at runtime:
Unlike the macro, hegel::test() cannot derive a name for the test, so set Settings::database_key yourself if you want failures persisted to the example database and replayed across runs.