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 timesREQUIRE
: a single assertionGENERATE
: 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: