Advent 2021: Catch2
This blog is part of the 24 posts long series "Advent 2021":
- Advent 2021: Intro (December 01, 2021)
- Advent 2021: C++ (December 02, 2021)
- Advent 2021: C# (December 03, 2021)
- Advent 2021: Python (December 04, 2021)
- Advent 2021: Go (December 05, 2021)
- Advent 2021: TypeScript (December 06, 2021)
- Advent 2021: CMake (December 07, 2021)
- Advent 2021: Django (December 08, 2021)
- Advent 2021: Angular (December 09, 2021)
- Advent 2021: Flask (December 10, 2021)
- Advent 2021: gRPC (December 11, 2021)
- Advent 2021: GraphQL (December 12, 2021)
- Advent 2021: XML & JSON (December 13, 2021)
- Advent 2021: Matplotlib, Pandas & Numpy (December 14, 2021)
- Advent 2021: Linux (December 15, 2021)
- Advent 2021: Ansible (December 16, 2021)
- Advent 2021: SQLite (December 17, 2021)
- Advent 2021: Catch2 (December 18, 2021)
- Advent 2021: Zstandard (December 19, 2021)
- Advent 2021: ZFS (December 20, 2021)
- Advent 2021: Thunderbird (December 21, 2021)
- Advent 2021: Visual Studio Code (December 22, 2021)
- Advent 2021: Blender (December 23, 2021)
- Advent 2021: Open source (December 24, 2021)
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.