C++: Self-registering functions using macros for test libraries

0 min read (150 words)
Tutorials C++

Google’s C++ testing library has a nice syntax for registering tests, without needing to remember to add the tests to some central index. This article will show how to use macros to allow the creation of tests using only the following code:

Test(IntegerComparisonTest) {
    int a = 3;
    assert(a == 3);

Whilst this gives the appearance of GTest adding a new keyword, Test() is actually a macro. After the preprocessor stage, the code will look something like this:

class Test_MyTest {
    void execute();

    static Test_MyTest create() { return Test_MyTest(); }
    static bool registered = TestFactory::Register("MyTest", &Test_MyTest::create)

void Test_MyTest::execute() {
    int a = 3;
    assert(a == 3);

The thing to note here is that the test body isn’t actually part of the macro, and that it utilises a split declaration and definition.

Simpler Approach

Now, this may be necessary in GTest’s use case to store some additional meta data in the class, but it’s not always necessary. It would be much cleaner to cut out the static create function and the unnecessary class. There’s a solution to this: function pointers!

bool test_MyTest();
static bool test_MyTest_registered = TestFactory::Register("MyTest", &test_MyTest);
bool test_MyTest() {
    int a = 3;
    assert(a == 3);


// ---- .hpp ----

#include <functional>

#define Test(name) \
    void test_##name(); \
    static bool test_##name##_registered = TestFactory::Register(#name, &test_##name); \
    void test_##name()

class TestFactory {
    static bool Register(std::string name, std::function<bool()> func);
    static std::map<std::string, std::function<bool()>> Tests;

// ---- .cpp ----

std::map<std::string, std::function<bool()>> TestRunner::Tests;

bool TestFactory::Register(std::string name, std::function<bool()> func) {
    auto it = Tests.find(name);
    if (it == Tests.end()) {
        Tests[name] = std::move(func);
        return true;
    return false;