aboutsummaryrefslogtreecommitdiffstats

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:

run 1

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:

run 2

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:

run 3

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.