/utest

An ultra-lightweight, micro unit test framework for C99 presented as a single-header

Primary LanguageCThe UnlicenseUnlicense

About

µTest is an ultra-lightweight micro unit test framework for C99. It is the sister framework of µTest for C++11.

Compiling

µTest is provided as a single-header library. To compile, you simply include the header where you need to and ensure that you provide the implementation by defining UTEST_C_IMPLEMENTATION in exactly one source file before you include the header.

	#define UTEST_C_IMPLEMENTATION
	#include "utest.h"

Usage

Before executing a test, call:

utest_init();

Specify tests as functions with the signature: void [func_name](void)

A convenience macro TEST has been defined for quick definition of tests

eg:

 TEST(my_test)
 {
	// do nothing
 }

Tests can be run individually via TEST_RUN()

eg:

int result = TEST_RUN(my_test);

A test fails if it returns a non-zero result. You can check the failure message by calling utest_last_msg().

Assertion

µTest provides several simple assertion macros for use in tests.

// Fail the test
TEST_FAIL()

// Fail the test with a message		
TEST_FAIL_MESSAGE(message)

// Assert expr == true	
TEST_ASSERT(expr)

// Assert signed 8-bit int value actual == expected
// There are INT8, INT16, INT32 and INT64 variants of this macro
TEST_ASSERT_EQUAL_INT8(expected, actual)

// Assert unsigned 8-bit int value actual == expected
// There are UINT8, UINT16, UINT32 and UINT64 variants of this macro
TEST_ASSERT_EQUAL_UINT8(expected, actual)

// Assert float value actual is within epsilon of expected
TEST_ASSERT_EQUAL_FLOAT(expected, actual, epsilon)
TEST_ASSERT_EQUAL_DOUBLE(expected, actual, epsilon)

// Assert string value actual == expected
TEST_ASSERT_EQUAL_STRING(expected, actual)
TEST_ASSERT_EQUAL_NOCASE_STRING(expected, actual)

// Print an information message from this test (does not fail)
TEST_MESSAGE(message) 

Note: The assert/fail macros do not raise asserts - instead they record the failure message and terminate the test.

Fixtures

Often groups of related tests will be arranged into fixtures, using the macros below.

TEST_FIXTURE_BEGIN(mytest_fixture)
	TEST_FIXTURE_TEST(mytest_a)
	TEST_FIXTURE_TEST(mytest_b)
	TEST_FIXTURE_TEST(mytest_c)
TEST_FIXTURE_END()

Fixtures can be run by calling TEST_RUN_FIXTURE()

eg:

int result = TEST_RUN_FIXTURE(mytest_fixture);

Fixtures support per-test setup/teardown functions that are called before/after each test execution respectively. This is useful to perform any common initialization and cleanup logic that wraps each test. These functions have the same signature as a test (eg: void [func_name](void)).

To declare a fixture with a test setup/teardown method...

void myfixture_test_setup()
{
	// do work before test
}

void myfixture_test_teardown()
{
	// do work after test
}

TEST_FIXTURE_BEGIN_F(mytest_fixture, myfixture_test_setup, myfixture_test_teardown)
	TEST_FIXTURE_TEST(mytest_a)
	TEST_FIXTURE_TEST(mytest_b)
	TEST_FIXTURE_TEST(mytest_c)
TEST_FIXTURE_END()

Fixtures also support fixture-level setup/teardown functions that are run before/after any tests. This is useful To perform any one time initialization and cleanup logic that needs to be run before anything else. These functions have the same signature as a test (eg: void [func_name](void)).

To declare a fixture with a fixture setup/teardown method...

void myfixture_setup()
{
	// do work before any tests
}

void myfixture_teardown()
{
	// do work after any tests
}

TEST_FIXTURE_BEGIN_G(mytest_fixture, myfixture_setup, myfixture_teardown)
	TEST_FIXTURE_TEST(mytest_a)
	TEST_FIXTURE_TEST(mytest_b)
	TEST_FIXTURE_TEST(mytest_c)
TEST_FIXTURE_END()

It is possible to combine both fixture setup and per-test setup functions by as follows:

TEST_FIXTURE_BEGIN_A(mytest_fixture, myfixture_setup, myfixture_teardown, myfixture_test_setup, myfixture_test_teardown)
	TEST_FIXTURE_TEST(mytest_a)
	TEST_FIXTURE_TEST(mytest_b)
	TEST_FIXTURE_TEST(mytest_c)
TEST_FIXTURE_END()

This will yield the execution flow detailed here:

myfixture_setup()

	myfixture_test_setup()
		mytest_a()
	myfixture_test_teardown()

	myfixture_test_setup()
		mytest_b()
	myfixture_test_teardown()

	myfixture_test_setup()
		mytest_c()
	myfixture_test_teardown()

myfixture_teardown()

Configuration

Define UTEST_MAX_TESTS_PER_FIXTURE before including the header to change the number of tests allowed per fixture.

Define UTEST_MSG_BUFFER_SIZE to a different size if you need to change the size of the error message buffer.

Utility

Use utest_set_user(void*) and utest_get_user to store and retrieve global data. This is useful for storing data to be used in a test.

You can hook the results of a test execution by passing a function with the signature void(func)(const utest_fixture*, const utest_entry*, utest_result, const char*) to utest_set_result_func. You will receive information about the test, its parent fixture, the execution status and the assert message if it failed. This is useful if you wish to embed µTest in your own application or test runner framework.

Similarly, you can hook the results of a test TEST_MESSAGE by passing a function with the signature void(func)(const utest_entry*, const char*) to utest_set_print_func.

Caveats

Tests execution is wrapped in setjmp & longjmp. As a result it can cause issues with stack unwinding and destructors in C++ (especially if you're using RAII). Be aware of the potential issues of this when writing tests and allocate any resources within test setup/teardown functions.

Future

Planned features (in no order):

  • More assert macros
  • Usability improvements

License

µTest is free and unencumbered software released into the public domain. See UNLICENSE for details.