Like NUnit or JUnit, but tiny header only, and written in C for C development.
- Header Only: the library file is single and ready to mingle!
- Process Isolation: each test runs in an isolated process, allowing all tests to run even if one of them crashed.
- Automatic Registration: suites, and tests are registered automatically. No need for a
main()function
Each tests file represents a single testing-suite. Use the CUNIT_TEST macro in order to define a test, and use the macros: CUNIT_SETUP, CUNIT_CLEANUP, CUNIT_SETUP_ONETIME, CUNIT_CLEANUP_ONETIME to define functions that would run before and after every test in the current suite (file of tests). Here is how a complete testing suite might look:
#include <stdio.h>
#define CUNIT_IMPLEMENTATION
#include "../lib/cunit.h"
CUNIT_SETUP_ONETIME()
{
printf("Establish connection to DB....\n");
}
CUNIT_CLEANUP_ONETIME()
{
printf("Terminate connection to DB....\n");
}
CUNIT_SETUP()
{
printf("Create DB entries for test....\n");
}
CUNIT_CLEANUP()
{
printf("Delete DB after test....\n");
}
CUNIT_TEST(str_eq)
{
CUNIT_ASSERT_STR_EQ("Hello World", "Hello World");
CUNIT_EXPECT_STR_NEQ(NULL, "Bimba");
CUNIT_EXPECT_STR_NEQ(NULL, NULL);
}The default timeout duration is 3 seconds. However that can easily be changed by defining:
#define CUNIT_USER_TIMEOUT_MS 10000 // <-- insert your timeout in milliseconds, e.g: 10000 for 10 seconds
...
#define CUNIT_IMPLEMENTATION
#include ".../cunit.h"The library defines a default main function, which takes care of running the tests and de-allocating memory (de-allocating memory also happens in the destructor) and runs it without requiring the user to do anything. Should the user wish to define his own, custom main function (even though I cannot seem to find any probable reason for him to wish to do so), he may simply define:
#define CUNIT_USE_CUSTOM_MAIN
...
#define CUNIT_IMPLEMENTATION
#include ".../cunit.h"and then add a definition for a main function in his own code.
The library uses two types of assertions, those starting with ASSERT are fatal, and will cause the test to terminate prematurely when failed and those starting with EXPECT would only log the error, without causing the test to fail.
| Fatal Assertion | Non-Fatal Expectation | Description |
|---|---|---|
CUNIT_ASSERT_TRUE(cond) |
CUNIT_EXPECT_TRUE(cond) |
Fails if cond is 0 (False). |
CUNIT_ASSERT_FALSE(cond) |
CUNIT_EXPECT_FALSE(cond) |
Fails if cond is non-zero (True). |
Used for comparing standard integer types.
| Fatal Assertion | Non-Fatal Expectation | Description |
|---|---|---|
CUNIT_ASSERT_INT_EQ(a, b) |
CUNIT_EXPECT_INT_EQ(a, b) |
Verifies a == b |
CUNIT_ASSERT_INT_NEQ(a, b) |
CUNIT_EXPECT_INT_NEQ(a, b) |
Verifies a != b |
CUNIT_ASSERT_INT_LEQ(a, b) |
CUNIT_EXPECT_INT_LEQ(a, b) |
Verifies a <= b |
CUNIT_ASSERT_INT_LOWER(a, b) |
CUNIT_EXPECT_INT_LOWER(a, b) |
Verifies a < b |
Used for comparing floats/doubles.
- Standard: Uses
CUNIT_DEFAULT_THRESHOLDfor precision. - Threshold: Allows you to specify a custom
threshold(epsilon/tolerance).
| Fatal Assertion | Non-Fatal Expectation | Description |
|---|---|---|
CUNIT_ASSERT_FLOAT_EQ(a, b) |
CUNIT_EXPECT_FLOAT_EQ(a, b) |
Verifies a equals b (default tolerance). |
CUNIT_ASSERT_FLOAT_EQ_THRESHOLD(a, b, t) |
CUNIT_EXPECT_FLOAT_EQ_THRESHOLD(a, b, t) |
Verifies a equals b (tolerance t). |
CUNIT_ASSERT_FLOAT_NEQ(a, b) |
CUNIT_EXPECT_FLOAT_NEQ(a, b) |
Verifies a does not equal b. |
CUNIT_ASSERT_FLOAT_LEQ(a, b) |
CUNIT_EXPECT_FLOAT_LEQ(a, b) |
Verifies a <= b (default tolerance). |
CUNIT_ASSERT_FLOAT_LEQ_THRESHOLD(a, b, t) |
CUNIT_EXPECT_FLOAT_LEQ_THRESHOLD(a, b, t) |
Verifies a <= b (tolerance t). |
CUNIT_ASSERT_FLOAT_LOWER(a, b) |
CUNIT_EXPECT_FLOAT_LOWER(a, b) |
Verifies a < b (default tolerance). |
CUNIT_ASSERT_FLOAT_LOWER_THRESHOLD(a, b, t) |
CUNIT_EXPECT_FLOAT_LOWER_THRESHOLD(a, b, t) |
Verifies a < b (tolerance t). |
Used for C-style null-terminated strings (char*).
| Fatal Assertion | Non-Fatal Expectation | Description |
|---|---|---|
CUNIT_ASSERT_STR_EQ(a, b) |
CUNIT_EXPECT_STR_EQ(a, b) |
Verifies string a is identical to string b. |
CUNIT_ASSERT_STR_NEQ(a, b) |
CUNIT_EXPECT_STR_NEQ(a, b) |
Verifies string a is different from string b. |
CUNIT_ASSERT_STR_CONTAINS(a, b) |
CUNIT_EXPECT_STR_CONTAINS(a, b) |
Verifies string a contains substring b. |
Used for checking memory addresses and NULL states.
| Fatal Assertion | Non-Fatal Expectation | Description |
|---|---|---|
CUNIT_ASSERT_PTR_EQ(a, b) |
CUNIT_EXPECT_PTR_EQ(a, b) |
Verifies pointer a == pointer b. |
CUNIT_ASSERT_PTR_NEQ(a, b) |
CUNIT_EXPECT_PTR_NEQ(a, b) |
Verifies pointer a != pointer b. |
CUNIT_ASSERT_PTR_NULL(a) |
CUNIT_EXPECT_PTR_NULL(a) |
Verifies pointer a is NULL. |
CUNIT_ASSERT_PTR_NOT_NULL(a) |
CUNIT_EXPECT_PTR_NOT_NULL(a) |
Verifies pointer a is not NULL. |
Used for comparing raw blocks of memory (like memcmp).
| Fatal Assertion | Non-Fatal Expectation | Description |
|---|---|---|
CUNIT_ASSERT_MEM_EQ(a, b, size) |
CUNIT_EXPECT_MEM_EQ(a, b, size) |
Verifies the first size bytes of a and b are identical. |
CUNIT_ASSERT_MEM_NEQ(a, b, size) |
CUNIT_EXPECT_MEM_NEQ(a, b, size) |
Verifies the first size bytes of a and b are different. |
In my experience (which is not THAT vast) the predominant majority of unit-tests are quite simple, they revolve around numerical comparisons and assertions and only seldom do they ever crash. For this reason I believe that one of the most distinct features of a testing-library, instead of just writing a main function which goes over standard tests, is having the ability to register tests neatly. In this project, I used the __attribute(constructor)__ feature, available in modern compilers, which, coupled with some pre-processing magic, enabled me to create the following API for registering a test:
CUNIT_TEST(compare_numbers)
{
int x = 5;
int y = 3;
ASSERT_INT_LEQ(x,y); // assert x <= y
}Under the hood, the CUNIT_TEST macro looks almost like this:
#define CUNIT_TEST(func) \
void func(void); \
__attribute__((constructor)) \
void register_##func() \
{ \
cunit_register_test(func, #func); \
} \
void func(void) \There's a bit to unpack here, but mostly what it does is:
- Take a function name
func - Declare the function with
voidtype for compatibility with older C versions - Create a function, with the
__attribute__(constructor), that would register the test BEFORE main runs. - Then create the actual function
func. The body of the function is what follows the macro invocation.
If this magic did not bring a smile to your face, I don't know what would ;)
(*) in practice, I use static and other tiny alterations to allow defining multiple tests with the same name under different suites, but the above is the gist of it
Each test is ran in a different process, in order to minimise friction between the tests.