![]() |
Maat is a lightweight, self-contained, single-header C++ test harness with no dependencies and no boilerplate. Drop in the header, write your tests, run them. |
|---|
Maat is the Egyptian goddess of truth, justice, and cosmic order. In the Weighing of the Heart the heart of the deceased is weighed against a feather before Osiris, Thoth and Anubis. The scales do not negotiate. The result is binary. Failed tests are eaten by the Ammit monster. |
A test harness should work the same way.
The C++ testing landscape is paradoxical. There are excellent, mature, well-documented frameworks — and yet setting up a test for a small utility function can consume more time than writing the function itself.
Google Test requires a CMake fetch, a linked library, and a registration mechanism before anything is verified. Catch2 and doctest are more self-contained but arrive with their own DSLs, their own main(), and enough machinery that understanding what is actually happening requires reading documentation rather than code. Boost.Test is Boost — which says everything. CppUnit faithfully mirrors JUnit's Java architecture, which is a questionable virtue in a language that isn't Java.
The common thread is that these frameworks were built to scale — to enterprise codebases, CI pipelines, and teams with dedicated QA infrastructure. That scaling comes at a cost that small and mid-sized projects simply shouldn't have to pay.
What was needed was something closer to the metal: assertions that are just macros, tests that are just functions, a runner that is just a loop, and a report that is just output. No framework. No discovery. No opinions. Maat is that. The scales are simple by design — it is the judgment that matters, not the apparatus.
Copy maat.h into your project and include it:
#include "maat.h"
That's it.
Maat provides two classes of assertion, reflecting terminal and fatal failures. All take a message that ia displayed if the test fails:
REQUIRE — hard assertion. On failure the test terminates immediately, as a heart too heavy for the feather ends the ceremony:
REQUIRE(ptr != nullptr, "pointer must not be null");
REQ_EQ(result, expected, "values must match");
REQ_NEQ(result, forbidden, "value must differ");EXPECT — soft assertion. On failure the verdict is recorded but execution continues to allow additional failures to be recorded before the test is reported failed:
EXPECT(value > 0, "value should be positive");
EXP_EQ(result, expected, "values should match");
EXP_NEQ(result, forbidden, "value should differ");NOTE — inserts a message into the test report without affecting the result:
NOTE("testing edge case: empty input");RUN_TEST(my_test_function);Test functions return bool and take no arguments:
bool my_test_function()
{
REQUIRE(true, "this will pass");
EXPECT(1 + 1 == 2, "arithmetic holds");
return true;
}Returning false from the test function is equivalent to a failed REQUIRE.
Call maat::print_summary() at the end of your test run:
maat::print_summary();Output:
Summary: 7 passed, 1 failed, 8 total.
#include "maat.h"
#include <vector>
bool test_vector_basics()
{
std::vector<int> v;
REQUIRE(v.empty(), "vector should start empty");
v.push_back(42);
EXP_EQ(v.size(), 1u, "vector should have one element");
EXP_EQ(v[0], 42, "element should be 42");
return true;
}
int main()
{
RUN_TEST(test_vector_basics);
maat::print_summary();
}Tests report live as they run, with the status overwriting the "running" indicator:
[ ok ] 1: test_vector_basics
[FAIL] 2: test_something_else
- Exp: values should match
- Req: pointer must not be null
Failed assertions are reported beneath the test name — Req: for hard failures that terminated the test, Exp: for soft failures that were accumulated. Ammit is not selective.
All Maat internals live under ::maat. Your test files need not reference the namespace directly — the macros handle everything.
Licensed under the MIT License.
