simple-test
simple-test.h is a single header file which implements quick-and-dirty unit testing for C. robust unit testing suites for C exist, but they require a degree of boilerplate which can be prohibitive for their use in spare-time projects. if you're writing something for fun, lots of boilerplate defeats the purpose of writing it. simple-test is made to be as easy to use as possible: include the header, compile, and run.
note: although it has no dependencies outside the standard library, this
project makes heavy use of _Generic
, so C11 support is required.
brief summary
test suites are to be written as a single .c file separate from the body of
functionality to be tested. include the functionality needed (including headers
and linking against objects) to produce an executable file from the .c file,
then run the executable to test. simple-test internally defines a main
function, so none is required, and the argc
and argv
arguments are made
available to all tests.
example
the following example file is available as example.c, which you can compile and play with yourself.
/* SIMPLE_TEST_PRINT_FUN may be redefined like so. default is `printf` */
#define stderr_redirect(...) fprintf(stderr, __VA_ARGS__)
#define SIMPLE_TEST_PRINT_FUN stderr_redirect
/* colour output is on by default, but can be disabled */
/* #define SIMPLE_TEST_USE_COLOUR false */
#include "../simple-test.h"
/* global variables, functions, and includes must come before BEGIN_TEST */
char *global_s;
/* a teardown function must be of type `void function(void)` and can be called
* by TESTs which include them using the TEARDOWN macro. the call occurs either
* after an ASSERT fails or after the TEST is finished.
*
* the macro may be called multiple times, but only the most recently set
* teardown function will be called.
*
* here i'm using it to free memory that's alloced inside tests below */
void teardown_fun(void)
{
free(global_s);
}
/* must appear before an (optional) REGISTER_TEARDOWN and all TESTs */
BEGIN_TEST
/* run a test. provided description must be a string literal */
TEST("basic assertion")
{
/* ASSERT fails if passed some sort of non-true value (0, false, NULL) */
ASSERT(1);
}
TEST("basic not assertion")
{
bool b = false;
ASSERT_NOT(b);
}
TEST("boolean comparison")
{
bool a = false, b = true;
/* fail if parameters are not equal */
ASSERT_EQ(a, b);
}
TEST("type mismatch")
{
char a = 'a';
int i = 97;
char *b = NULL;
/* for convenience's sake, when presented with mismatched types, assertions
* try to resolve them in a way that's most likely to match the
* programmer's intentions. here 'i' is interpreted as a char */
ASSERT_EQ(a, i);
/* if there isn't a straightforward comparison to make, though (as is the
* case here, with a 'char' and 'char *'), a type mismatch error occurs */
ASSERT_EQ(a, b);
}
TEST("ECHO example")
{
int i;
/* ECHO can be used to neatly report information during a run */
if (true)
ECHO("loop until i not less than 1");
for (i = 0; i < 2; i++) {
/* it takes printf format strings and variable args as well */
ECHO("i == %d", i);
ASSERT_LT(i, 1);
}
}
TEST("string comparison")
{
char *s = "test";
global_s = strdup("test");
/* this tells this test to call the previously defined teardown function on
* exiting (successfully or otherwise) */
TEARDOWN(teardown_fun);
/* strings are compared by content, so this assertion succeeds */
ASSERT_EQ(s, global_s);
}
TEST("pointer comparison")
{
char *s = "test";
global_s = strdup("test");
TEARDOWN(teardown_fun);
/* you can cast parameters in order to use a different type of comparison.
* here i'm casting the 'char *' to 'void *' so assertion performs a
* pointer comparison, which fails */
ASSERT_EQ((void*)s, (void*)global_s);
}
/* must come after all TESTs */
END_TEST
on compiling and running this file, the output will look something like this:
an error occurs because the types passed to the second assertion in the "type
mismatch" test are... well, mismatched. assertions do their best to accommodate
mismatched types when it makes sense to do so (e.g. comparing a signed and
unsigned integer), but there is no meaningful comparison to be made between a
char
and a char *
, so the error is returned.
if that assertion is commented out:
/* if there isn't a straightforward comparison to make, though (as is the
* case here, with a 'char' and 'char *'), a type mismatch error occurs */
/* ASSERT_EQ(a, b); */
and the tests re-run, the output would look like this:
as you can see, there are multiple failing assertions. their failures are reported and a total count of failing tests printed. if all of these failing asserts are commented out and the tests re-run, the output looks like this:
hooray! ^_^
interface
simple-test's interface consists of a series of preprocessor macros:
NAME | DESCRIPTION |
---|---|
SIMPLE_TEST_USE_COLOUR | may be defined, before the header is included, as either a true or false resolving expression. default is true |
SIMPLE_TEST_PRINT_FUN | may be defined, before the header is included, as a function or macro which takes printf style arguments. default is printf |
BEGIN_TEST | must be included once, after includes and global declarations and before the first TEST statement |
END_TEST | must be included once, after the final TEST statement |
TEST(description){} | declare a test, described by description , which consists of statements between the {} |
TEARDOWN(fun); | if included within a TEST body, the designated TEST will call the function argument after any following failed assertions or when terminating. may be redefined |
ECHO(...); | print a formatted description of the state within the current test |
assertions are used to check that the behaviour of code executed inside the
body of a TEST
statement is correct. values are passed and the assertion
fails if those values do not reflect expectations. valid assertions are:
NAME | DESCRIPTION |
---|---|
ASSERT(a); | assert that the value of a is not a "not" value, i.e. NULL, 0, or '\0' |
ASSERT_NOT(a); | assert that the value of a is a "not" value |
ASSERT_EQ(a, b); | fail if the values of a and b are not equal |
ASSERT_NEQ(a, b); | fail if the values of a and b are equal |
ASSERT_GT(a, b); | fail if the value of a is not greater the value of b |
ASSERT_GEQ(a, b); | fail if the value of a is not greater than or equal to the value of b |
ASSERT_LT(a, b); | fail if the value of a is not less than the value of b |
ASSERT_LEQ(a, b); | fail if the value of a is not less than or equal to the value of b |
note
simple-test.h internally uses macros prefixed with SIMPLE_TEST_
and functions
and variables prefixed with simple_test_
. these should not be used directly.