Single-header unit testing for c
expectandrefute/expect_notfor boolean assertions- automatic test runner, no need to run tests individually
- type-generic assertions with expected/actual values printed on failure
- test grouping with
describeandshould/it - setup and teardown hooks with
beforeandafter skip,pending, andonlyfor test control flow- crash recovery, segfaults and aborts don't kill the runner
- configurable test output verbosity levels
- tracks time taken to run tests
- no dependencies, just copy the header file
- simple and intuitive user api
A lot of C testing frameworks feel like C, perhaps verbose, boilerplate-heavy, or too low-level.
claim is inspired by Ruby's RSpec and Minitest, bringing constructs like describe, should, before, and after to C. The goal is a testing framework that reads like English while also providing a useful collection of tools.
This framework is designed around a single translation unit. If you would like to run tests from different files using this framework, then you will have to compile them separately and then run each of them individually.
This is undesirable and part of future work is to rectify this.
Notice: claim is a new project and may contain bugs or stability issues. Use with caution.
Copy claim.h into your project and include it.
#include "claim.h"
describe("math")
should ("add") {
expect(1 + 1 == 2);
expect(0 + 0 == 0);
}
should ("subtract") {
expect(5 - 3 == 2);
refute(10 - 1 == 0);
}
int main() {
return test_results(CLAIM_VERBOSE);
}outputs:
math
~ add (0.3ms)
~ subtract (0.3ms)
2 tests, 2 passed, 0 failed (0 pending, 0 skipped) in 0.7msTests register themselves. Just write them and they run.
All test functions are declared with should or it. They're interchangeable so use whichever you prefer.
should ("add two numbers") {
expect_eq(1 + 1, 2);
}
it ("subtracts two numbers") {
expect_eq(5 - 3, 2);
}expect(x == 1) // asserts any expression
refute(x == 0) // fails if expression is true (alias: expect_not)
expect_eq(a, b) // type-generic equality (int, float, char *, etc.)
expect_not_eq(a, b) // type-generic inequality
expect_null(ptr) // ptr == NULL
expect_not_null(ptr) // ptr != NULLdescribe groups tests. Test failures show which group they belong to. It applies to all tests below the describe until another is declared.
describe("parser")
should ("parse int") {
int result = parse("42");
expect_eq(result, 42);
}
should ("reject empty") {
int result = parse("");
expect_eq(result, -1);
}Failure output:
parser
x parse int (0.4ms)
assertion failed (test.c:5): expected 'result' to equal '42' (got -1, expected 42)Mark tests as 'work-in-progress' or 'todo' with pending(). They won't run and are tracked separately.
should ("not ready yet") {
pending();
// nothing below here runs
} pending tests
- not ready yet (0.1ms)
2 tests, 2 passed, 0 failed (1 pending, 0 skipped) in 0.8msSkip a test at runtime with skip(). Use it when the test is valid but can't run right now, like with a broken external service, a known bug waiting to be fixed, or a platform-specific test.
describe("storage")
should ("read from cache") {
skip("currently blocked by ticket #12 — cache returns stale entries");
expect_eq(cache_get("key"), "xxx");
} storage
- read from cache (0.2ms)
'currently blocked by ticket #12 — cache returns stale entries'
3 tests, 2 passed, 0 failed (0 pending, 1 skipped) in 1.2msRun specific tests with only. All other tests are skipped.
should ("not do this") {
expect(true);
}
only ("do this") {
expect_eq(1 + 1, 2);
}
should ("not do this either") {
expect(true);
}You can have multiple tests declared with only.
1 tests, 1 passed, 0 failed (0 pending, 2 skipped) in 0.5msbefore runs before every test in a group. after runs after. You might use them to allocate and free shared state.
static int *counter;
describe("counter")
before ("setup") {
counter = malloc(sizeof(int));
*counter = 0;
}
after ("cleanup") {
free(counter);
counter = NULL;
}
should ("start at zero") {
expect_eq(*counter, 0);
}
should ("increment") {
*counter = 42;
expect_eq(*counter, 42);
}
should ("reset between tests") {
expect_eq(*counter, 0);
}In this case, each test gets a new allocation. before and after callbacks are scoped to the current describe. Declaring a new describe clears them.
after and before will only run when inside of a describe. They must also be positioned above all tests for them to work.
all passing:
math
~ add (0.3ms)
~ subtract (0.2ms)
2 tests, 2 passed, 0 failed (0 pending, 0 skipped) in 0.8mswith failures:
math
~ subtract (0.2ms)
x add (0.4ms)
assertion failed (test.c:6): expected 'a' to equal 'b' (got 3, expected 5)
2 tests, 1 passed, 1 failed (0 pending, 0 skipped) in 1.0mstest_results returns 1 on failure and 0 on success.
Each test runs in a forked process. If a test segfaults, aborts, or crashes, the runner catches it and keeps going.
parser
x parse null (0.3ms)
crashed (SIGSEGV (segmentation fault))
3 tests, 2 passed, 1 failed (0 pending, 0 skipped) in 1.8msHandles SIGSEGV, SIGABRT, SIGFPE, and SIGBUS.
Control how much output test_results produces by passing a verbosity level.
test_results(CLAIM_VVV) // full output
test_results(CLAIM_VV) // failures and crashes only
test_results(CLAIM_V) // summary line only
test_results(CLAIM_SILENT) // no output, just the exit codeRun the tests with:
make testBuild project with:
make