C++ unit testing with Catch2

Last time I used C++ for doing anything serious, I remember moving from Google Tests to the excellent doctest. Unfortunately I don’t remember exactly the reasons, but I know that with time I started to grow the feeling that Google Test was no longer the best option (maybe for lack of support of modern C++ features?), and I wanted to try something different. And again, I don’t remember the details (that’s the reason I want to keep this blog now!) but doctest felt much better and more modern.

Being doctest a reimplementation of catch with focus on compilation speed, this time I wanted to check how Catch2 fares in comparison.

To be honest, I am not sure if that was a good idea :)
It’s a bit too early to make an assessment on the two different frameworks; however, while doctest requires just to download the header and import it in the test files, Catch2 involves several build steps, thus requiring to create a complete makefile.
Normally this is not a big issue; however, in my case it required some additional exercises, and much, much more work just to get the test done.

Installation

I started by following the instruction from catch2, unfortunately they were not really helpful and I got several errors.
So I tried to use FetchContent and it was a bit better (at least cmake was not breaking), but still not working: e.g. visual studio code said that I don’t have catch2/catch_test_macros.hpp. The problem was that it was in some “private” directory (see also [adding libraries in CMake] (https://rob.liffredo.net/posts/using-cmake/#adding_libraries “Using CMake”)). Eventually, I decided to install it globally with homebrew, and finally I was able to make it work.

Basic usage

Once installed, I have to say that the usage is very straightforward.

  • TEST_CASE: a single test.
  • SECTION: a way to restore state inside a test. This is useful when creating some fixture, and then using it again several times
  • REQUIRE: a single assertion
  • GENERATE: a way to create a “parametrized test”, like in pytest
#include <catch2/catch_test_macros.hpp>

unsigned int factorial( unsigned int number ) {
    return number <= 1 ? 1 : Factorial(number-1)*number;
}

TEST_CASE( "Factorials are computed", "[factorial]" ) {
    REQUIRE( factorial(0) == 1 );
    REQUIRE( factorial(1) == 1 );
    REQUIRE( factorial(2) == 2 );
    REQUIRE( factorial(3) == 6 );
    REQUIRE( factorial(10) == 3628800 );
    
    auto i = GENERATE(-10, 0, 10, 15, 50, 100);
    REQUIRE( factorial(i) >= 1);
    
    int j = 1;
    SECTION("Alter some value") {
        REQUIRE( j == 1 );
        j = 2;
        REQUIRE( j == 2 );
    }
    REQUIRE( j == 1 );
}

Test runner is indeed very fast, and official documentation is plentiful.

Overall, I am not sure if I liked it more than doctest. The latter is much simpler to inject in a project, without any additional hassle. That said, even if somehow less powerful than pytest’s approach, the ability to easily parametrize tests is a huge bonus – in doctest, parametrization is possible, but not really “native”.

Further reference

Again, documentation is very detailed, even if sometimes might be confusing (e,.g. for the installation). That said, catch2 is quite common, so there are also several good introductions; in particular I liked: