~yerinalexey/ctest

Simple C unit testing library

7b7f259 Update README header

5 days ago

a813191 Improve getting started docs

6 days ago

#C testing

ctest is a simple unit testing library for C.

#Installation

$ make
# make install

Will install libctest.a into /usr/lib and ctest.h into /usr/include. This can be changed by using PREFIX and/or DESTDIR:

  Install into /usr/local/{lib,include}:
$ make install PREFIX=/usr/local

  For Arch or Alpine Linux packages:
$ make install DESTDIR="$pkgdir" PREFIX=/usr

#Getting started

The basic concept of ctest is to define some test functions, which check behaviour of existing code, and are then passed to the test runner which runs all of them and shows what has proven to be incorrect.

Test functions accept a special state argument, which is required by assertion functions, thus the required signature is:

void test_thing(struct ctest_state *state);

There are some utility functions for conditionally failing your test:

  • ctest_assert - asserts that the condition is true
  • ctest_assert_null - asserts that the given pointer is NULL
  • ctest_assert_nonnull - asserts that the given pointer is not NULL

They all accept ctest_state as the first argument. ctest_assert additionally accepts a diagnostic message after the condition.

Example usage:

static void
test_example(struct ctest_state *state)
{
	/* Should always pass, otherwise you have more issues */
	ctest_assert(state, 2 + 2 == 4, "2 + 2 = 4");

	/* Remember that "strings" are pointers to char? */
	ctest_assert_nonnull(state, "Hello world");
}

The actual test runner is ctest_execute. It accepts a list (pointer + count) of ctest_test objects and runs them.

ctest_test holds the test function along with additional metadata:

  • name - name of the test that appears in the results
  • fn - pointer to the function with signature above
  • todo - marks the test as TODO, it will be skipped
  • fatal - marks the test as "sanity check" which will prevent anything else from running if it fails

Tests can be initialized with the following syntax (valid in C99 and later):

struct ctest_test tests[] = {
	{
		.name  = "add",
		.fn    = &test_add,
		.todo  = 0,
		.fatal = 0,
	},
	/* ... */
};

And running those is done like this:

ctest_execute(tests, sizeof(tests) / sizeof(tests[0]));

Note: sizeof(tests) / sizeof(tests[0]) is used to determine amount of tests (in case they are stack-allocated). sizeof(tests) returns size of an entire list of tests. sizeof(tests[0]) returns the size of a single test.

Complete example (see examples/add.c):

/*
 * To compile: cc add.c -o add -lctest
 */

#include <ctest.h>
#include <stdbool.h>

/*
 * Adds two numbers together.
 */
int
add(int a, int b)
{
	return a + b;
}

static void
test_add(struct ctest_state *state)
{
	ctest_assert(state, add(2, 2) == 4, "2 + 2 should be equal 4");
	/* 24 is the highest number by the way */
	ctest_assert(state, add(12, 12) == 24, "12 + 12 should be equal 24");
}

int
main()
{
	struct ctest_test tests[] = {
		{
			.name  = "add",
			.fn    = &test_add,
			.todo  = false,
			.fatal = false,
		},
	};

	ctest_execute(tests, sizeof(tests) / sizeof(tests[0]));
}

#API reference

#struct ctest_test

#include <ctest.h>
#include <stdbool.h>

struct ctest_test {
	/* Name of the test */
	char *name;

	/* Test function to be executed */
	testFn fn;

	/* Skip this test because it's not done yet */
	bool todo;

	/* Don't run anything after if this test fails */
	bool fatal;
};

Test definition.

Function pointed to by ctest_test.fn must have the following signature:

void name(struct ctest_state *);

ctest_test.todo signals that the test is not done yet and needs to be skipped. ctest_test.fn may be NULL in this case.

ctest_test.fatal will terminate further tests if the current fails. This is useful for smoke testing when failing tests for basic functions shouldn't cause other tests to run because they might break because of misbehaving basics.

#ctest_execute

#include <ctest.h>

void ctest_execute(struct ctest_test *tests, size_t ntests);

Runs provided tests and prints summary to the terminal.

#ctest_fail

#include <ctest.h>

void ctest_fail(struct ctest_state *state, char* msg);

Fails the test unconditionally with the a given diagnostic message. It's recommended against using this function directly, use helpers like ctest_assert instead.

#ctest_assert

#include <stdbool.h>
#include <ctest.h>

void ctest_assert(struct ctest_state *state, bool condition, char *msg);

Asserts that the condition is true and shows the given diagnostic message otherwise.

#ctest_assert_null, ctest_assert_nonnull

#include <ctest.h>

void ctest_assert_null(struct ctest_state *state, void *ptr);
void ctest_assert_nonnull(struct ctest_state *state, void *ptr);

ctest_assert_null asserts that the provided pointer is NULL.

ctest_assert_nonnull asserts that the provided pointer is not NULL.

#License

Mozilla Public License version 2. See https://www.mozilla.org/en-US/MPL