A simple yet powerful unit test library written in C.
- Hierarchical Organization: Organize tests in nested suites with unlimited depth
- Colored Output: ANSI color-coded status indicators
- Tree Structure: Unicode box-drawing characters for visual hierarchy
- Multiple Status Types: Success, errors, and expected failures
- Statistics: Automatic counting and display of test results
- Memory Safe: Robust memory management with error handling
- Performance Optimized: Efficient compilation and runtime performance
- Modular Design: Clean architecture following SOLID principles
| Symbol | Color | Meaning |
|---|---|---|
| K | Green | Success |
| K | Yellow | Unexpected output |
| B | Gray | Expected build error |
| B | Red | Build error |
| R | Gray | Expected runtime error |
| R | Red | Runtime error |
#include "unittest.h"
// define a test function
test_status_t my_test(void) {
// your test logic here
return STATUS_SUCCESS;
}
int main(void) {
// create test runner
test_runner_t* runner = test_runner_create();
if (!runner) {
fprintf(stderr, "Failed to create test runner\n");
return 1;
}
// create test suite
test_suite_t* suite = test_suite_create("My Test Suite");
if (!suite) {
test_runner_destroy(runner);
return 1;
}
// create and add test case
test_case_t* test = test_case_create("my_test", my_test);
if (test) {
test_suite_add_test_case(suite, test);
}
// add suite to runner and execute
test_runner_add_suite(runner, suite);
test_runner_run(runner);
// cleanup
test_runner_destroy(runner);
return 0;
}make all
make runTo build as a library:
make libunittest.a
sudo make installtest_runner_t* test_runner_create(void)- Create a new test runnervoid test_runner_destroy(test_runner_t* runner)- Clean up test runner and all associated datavoid test_runner_add_suite(test_runner_t* runner, test_suite_t* suite)- Add suite to runnervoid test_runner_run(test_runner_t* runner)- Execute all tests and display results
test_suite_t* test_suite_create(const char* name)- Create a new test suitevoid test_suite_destroy(test_suite_t* suite)- Clean up single test suite (children only)void test_suite_destroy_siblings(test_suite_t* suite)- Clean up entire sibling chainvoid test_suite_add_child(test_suite_t* parent, test_suite_t* child)- Add child suitevoid test_suite_add_test_case(test_suite_t* suite, test_case_t* test_case)- Add test case
test_case_t* test_case_create(const char* name, test_func_t test_func)- Create test casevoid test_case_destroy(test_case_t* test_case)- Clean up single test casevoid test_case_destroy_siblings(test_case_t* test_case)- Clean up entire sibling chainint test_case_add_result(test_case_t* test_case, test_status_t status)- Add single result (returns0on success)int test_case_add_results_va(test_case_t* test_case, int count, ...)- Add multiple results using variadic arguments
UNITTEST_SUITE(name)- Quick suite creationUNITTEST_CASE(name, func)- Quick test case creationUNITTEST_RUN(runner)- Quick test executionRESULTS(test_case, ...)- Efficient variadic results additionRESULTS_ARRAY(...)- Legacy array-based results (for backwards compatibility)
The library provides comprehensive error handling with return codes:
test_case_t* test = test_case_create("my_test", my_test_func);
if (!test) {
fprintf(stderr, "Failed to create test case\n");
return 1;
}
if (RESULTS(test, STATUS_SUCCESS, STATUS_BUILD_ERROR) != 0) {
fprintf(stderr, "Failed to add test results\n");
// handle error appropriately
}Create complex hierarchical structures:
test_suite_t* parent = test_suite_create("Parent Suite");
test_suite_t* child1 = test_suite_create("Child Suite 1");
test_suite_t* child2 = test_suite_create("Child Suite 2");
test_suite_t* grandchild = test_suite_create("Grandchild Suite");
test_suite_add_child(parent, child1);
test_suite_add_child(parent, child2);
test_suite_add_child(child1, grandchild);The RESULTS macro uses variadic functions for better performance:
test_case_t* test = test_case_create("multi_result_test", NULL);
if (test) {
RESULTS(test,
STATUS_SUCCESS,
STATUS_SUCCESS,
STATUS_EXPECTED_BUILD_ERROR,
STATUS_BUILD_ERROR,
STATUS_SUCCESS);
}test_status_t complex_test(void) {
// setup
void* resource = allocate_resource();
if (!resource) {
return STATUS_RUNTIME_ERROR;
}
// test logic
int result = perform_operation(resource);
// cleanup
free_resource(resource);
// assertion
return (result == EXPECTED_VALUE) ? STATUS_SUCCESS : STATUS_RUNTIME_ERROR;
}All memory allocations are checked and handled gracefully:
// the library handles allocation failures gracefully
test_runner_t* runner = test_runner_create();
if (!runner) {
// handle allocation failure
return 1;
}
// all destroy functions handle NULL pointers safely
test_runner_destroy(runner); // safe even if runner is NULL// good: check allocation success
test_runner_t* runner = test_runner_create();
if (!runner) {
return 1; // handle failure
}
// good: single cleanup call handles everything
test_runner_destroy(runner);
// The library automatically handles:
// - Sibling chains (next pointers)
// - Child hierarchies
// - Dynamic result arrays
// - String allocations- Linux: Fully tested with GCC
- macOS: Compatible (requires ANSI terminal support)
- Windows: Compatible with ANSI-enabled terminals or WSL
Functions return the following error codes:
0: Success-1: Memory allocation failureNULL: Object creation failure
The library is NOT thread-safe. If you need to run tests concurrently, create separate test runners for each thread.
