Principle:Duckdb Duckdb Test Framework
| Knowledge Sources | |
|---|---|
| Domains | Testing, Software_Engineering |
| Last Updated | 2026-02-07 12:00 GMT |
Overview
A header-based C++ unit testing framework that provides automatic test registration, expressive assertion macros, and hierarchical test organization through sections, enabling behavior-driven development testing patterns.
Description
Unit testing frameworks provide the infrastructure for writing, organizing, and executing automated tests that verify the correctness of individual components in a software system. A modern C++ testing framework must address several challenges: automatic discovery and registration of test cases (without manual test suite construction), expressive assertion syntax that produces clear failure messages, and flexible test organization that supports both flat test lists and hierarchical groupings.
The test case registration mechanism uses static initialization to register test functions at program startup. Each TEST_CASE macro defines a function and creates a static registrar object whose constructor adds the test to a global registry. At runtime, the test runner iterates over the registry, optionally filtering by tags or name patterns, and executes each test.
Assertion macros (REQUIRE, CHECK) evaluate expressions and capture both the original expression text and the evaluated values when a failure occurs. REQUIRE assertions are fatal (they abort the current test on failure), while CHECK assertions are non-fatal (they record the failure but continue execution). This dual mechanism allows tests to verify critical preconditions while still checking multiple properties.
BDD-style sections (SECTION) provide a mechanism for sharing setup code across related test cases. Within a TEST_CASE, multiple SECTIONs can be defined, and the framework re-executes the test once per leaf section, running the common setup code before each section. This is analogous to nested describe/it blocks in JavaScript testing frameworks and avoids code duplication for related test scenarios.
Usage
The test framework is used throughout DuckDB's C++ test suite for unit testing individual components. Tests verify the correctness of data structures, algorithms, type conversions, expression evaluation, and other internal components. The framework supports running specific tests by name or tag pattern, which is essential for development workflows where only a subset of tests needs to be executed after a code change.
Theoretical Basis
Automatic Test Registration: Static initialization pattern:
// Macro expands to a function + static registrar
TEST_CASE("addition works", "[math]") {
REQUIRE(1 + 1 == 2);
}
// Expands (conceptually) to:
static void test_func_42();
static AutoReg registrar_42(&test_func_42, "addition works", "[math]");
void test_func_42() {
REQUIRE(1 + 1 == 2);
}
// AutoReg constructor adds to global registry at startup:
AutoReg::AutoReg(func, name, tags) {
TestRegistry::instance().add(TestCase{func, name, tags});
}
Expression Decomposition: Capturing values in assertions:
// REQUIRE(a == b) must capture both 'a' and 'b' values
// Uses expression template / operator overloading trick:
REQUIRE(x == 42)
// Expands to:
do {
ExpressionDecomposer decomposer;
auto result = decomposer <= x == 42;
// ^^^^^^^^^^
// operator<= has lower precedence than ==
// so it captures the LHS before == executes
// Then operator== is overloaded to capture both sides
if (!result.succeeded()) {
report_failure("x == 42", x_value, "42", file, line);
abort_test(); // for REQUIRE (not for CHECK)
}
} while(false)
Section-Based Test Organization:
// Sections share setup, each section runs independently
TEST_CASE("vector operations") {
std::vector<int> v; // shared setup
v.push_back(1);
v.push_back(2);
SECTION("size is correct") {
REQUIRE(v.size() == 2);
}
SECTION("elements are accessible") {
REQUIRE(v[0] == 1);
REQUIRE(v[1] == 2);
}
SECTION("can be cleared") {
v.clear();
REQUIRE(v.empty());
}
}
// Framework runs TEST_CASE three times:
// Run 1: setup -> "size is correct"
// Run 2: setup -> "elements are accessible"
// Run 3: setup -> "can be cleared"
Tag-Based Filtering: <synttml lang="text"> // Tests can be tagged for selective execution TEST_CASE("name", "[tag1][tag2][!mayfail]") { ... }
// Run all tests: ./tests // Run tagged tests: ./tests [math] // Exclude tags: ./tests ~[slow] // Combine: ./tests [math]~[slow] // Special tags: [!mayfail], [!shouldfail], [!hide], [.] </syntaxhighlight>