I was looking for a unit testing library for C++, and I found many. Unfortunately, I decided I didn't want to use any of them. I decided to make my own.
This is the result. I made heavy use of the preprocessor, and followed the convention of leading underscore to mean non-published. Everything is done in all-caps, to try to follow good conventions and to avoid name collisions.
Please note that this code is published under the MIT license.
unittest.h
#pragma once
#include <vector>
#include <iostream>
// Helper Macros
#define _STR_HELP(x) #x
#define _STR(x) _STR_HELP(x)
#define ESCAPE(...) __VA_ARGS__ // TEST_CASE(std::map<int, int>) would fail. Wrap in this Macro to not fail.
#define _FILE_LINE __FILE__ ":" _STR(__LINE__)
// Assertion Macros
#define _GENERATE_MESSAGE(test) _FILE_LINE " Failed " #test "." // Message in almost every assertion
/*
* Assertions are intended to be used within a TEST_CASE macro. We return EXIT_FAILURE if we fail the assertion,
* meaning that the TEST_CASE fails. Assertions can also be used straight inside the main function, in which case
* the main function will exit. They are intended to be used within the TEST_CASE macro, so it's not recommended to do
* that.
*/
#define _ASSERT(test, message) if (!(test)) {\
std::cerr << message << std::endl;\
return EXIT_FAILURE;\
}
// One argument assertion. This generates a message to display.
#define _ASSERT_1(test) _ASSERT(test, _GENERATE_MESSAGE(test))
// Two argument assertion, which appends the user-defined message to the generated one.
#define _ASSERT_2(test, additional) _ASSERT(test, _GENERATE_MESSAGE(test) " " additional)
// Helper macro to get the correct assertion (out of _ASSERT_1 or _ASSERT_2)
#define _GET_ASSERTION(_1, _2, NAME, ...) NAME
// Public Macro for actually calling the ASSERT
#define ASSERT(...) _GET_ASSERTION(__VA_ARGS__, _ASSERT_2, _ASSERT_1)(__VA_ARGS__)
// More advanced macro than ASSERT; it tells the values of each of the arguments as part of the message.
#define ASSERT_EQ(a, b) {\
const auto& _VAL_1 = a;\
const auto& _VAL_2 = b;\
if (_VAL_1 != _VAL_2) {\
std::cerr << _FILE_LINE " Failed " #a " == " #b ". Actual Values:\n" \
#a ": " << _VAL_1 << "\n" \
#b ": " << _VAL_2 << std::endl;\
return EXIT_FAILURE;\
}\
}
/*
* Convenience struct for storing test cases and all related data into a vector.
*/
struct _TEST_CASE
{
const char *name;
int (*function)();
bool result;
};
std::vector<_TEST_CASE> _TEST_CASES; // All test cases are added to this global vector.
// Test Execution Macros
#define TEST_CASE(name, code) _TEST_CASES.push_back({ name, [](){ code; return EXIT_SUCCESS; }, false });
#define RUN_TESTS() std::cout << std::endl; \
for (_TEST_CASE& testCase : _TEST_CASES) {\
testCase.result = EXIT_SUCCESS == (*testCase.function)();\
if (!testCase.result) {\
std::cerr << "Failed test case " << testCase.name << "\n";\
}\
}\
std::cout << "Results: ";\
for (_TEST_CASE testCase : _TEST_CASES) {\
std::cout << (testCase.result ? '.' : 'F');\
}\
std::cout << std::endl << std::endl;