Advent 2021: Catch2
I’ve always been a huge proponent of unit testing. However, unit tests must be easy to write, otherwise, nobody will bother doing so. For Python, we have PyTest which makes it incredibly easy, but C++ didn’t use to be that lucky. There was Google Test, Boost.Test, and a few other small frameworks, but nothing “modern” which would come close in usability to the testing frameworks we enjoy in other languages … that is, until Catch2 came along.
Since it appeared it has been my goto test framework. Be it at work or in personal projects – after setting up a new (static) library, I set up a corresponding Catch2 unit test runner. Usage is so simple that even junior developers end up writing tests within minutes, it’s super fast to execute, and it has high quality failures messages without requiring large amounts of code and markup.
What’s so great about it? Two things really:
- Very few macros to remember. Most checks are of the form
CHECK(x == 23)
- Sections render test fixtures mostly obsolete
The section bit in particular is just so helpful it’s no longer funny:
IO::MemoryStream ms;
ms.Write ("ABCDEF", sizeof (char) * 7);
ms.Seek (0);
SECTION("Read to MutableArrayRef")
{
char buffer[7] = { 0 };
ms.Read(MutableArrayRef<>(buffer));
CHECK_THAT(buffer, Catch::Equals("ABCDEF"));
}
SECTION("Reading from closed stream fails", "[core]")
{
ms.Close();
byte buffer;
CHECK_THROWS_AS(ms.Read(&buffer, 1), IO::IOException);
}
SECTION("Read into null buffer fails", "[core]")
{
CHECK_THROWS_AS(ms.Read(nullptr, 3), RuntimeException);
}
Each SECTION
runs with the same setup code, so there’s no leaking of data between them, and no need to write fixture boilerplate. It feels really natural quickly and makes test development much quicker, especially as nearly every test requires some setup and then a few mutations. For me, Catch2 was a real eye opener as to how easy unit testing in C++ can be. It’s now an integral part of my C++ toolbox and I really can’t imagine working on a C++ project without it any more.