You can test C code without hiding control flow in macros.
This repository is a goofy illustration of that, written in GNU C23.
Macros tect_once
and tect_report
hide checking and reporting logic.
// example_one.c
#include "tect.h"
int check_for_tea_too() {
const int answer = 6 * 9;
if (!tect_once(answer == 42)) // Triggers at most _once_ by hiding state.
return tect_report("answer == %d", answer); // Appends to the output log.
if (!tect_once(3 == 4)) // We next check this.
return tect_report();
return 0; // => We are done. Other return values are yours to interpret.
}
int main() {
while (check_for_tea_too())
;
}
This outputs:
example_one.c:6: check_for_tea_too: !tect_once(answer == 42); answer == 54
example_one.c:9: check_for_tea_too: !tect_once(3 == 4);
Note that we run check_example
twice.
Returning and retrying gives you freedom to interpret and handle the returned
value however the language allows.
See example_two.c
for a more involved example.
This repeated-run style is inspired by (and not an implementation of) Andrei Alexandrescu's "Unit Test Should Nest", which is inspired by Catch2.
Common C testing tools use such powerful macros that their code does not resemble C.
They instead define new, domain-specific languages, because their macros hide both declarations and control flow.
For example, a pastiche of the C testing tools from Awesome C could look like:
DECLARE_TEST(context, "some associated text") {
CHECK(6 * 9 == 42);
CHECK(3 == 4);
}
By hiding core language features, such designs make me I feel uncomfortably incapable of composing these tools with core language features.
Our tect_*
macros also hide state and output.
And they are arguably disgusting abuses of language extensions.
But they do give users control of control flow.
Install tools in Ubuntu 23.04
:
sudo apt install clang-format gcc-13 make
Build and run our examples:
make
Other make commands:
make check # Run some basic tests on the examples' outputs.
make fmt # Auto-format our C source files.
make README.md # Generate `README.md` from the template file.
For development, please run make -s README.md fmt check
before each commit.
We generate this section from comments in tect.h
.
(expression) -> int
Return assertion's integral value, and print when it is first false.
You should follow tect_once
by calling tect_report
as follows:
int check_example() {
const int result = square(4);
if (!tect_once(result == 16))
return tect_report("result == %d", result);
if (!tect_once(square(0) == 0))
return tect_report();
return 0;
}
To fully run this test, use a loop such as:
while (check_example())
;
We track the first activation in a mutating static variable.
On a false assertion, our printed message adapts assert
's style.
(const char *format, ...args) -> int, or () -> int
Call printf
with any arguments, print '\n'
, and return (int) 1.
See tect_once
for usage.