This API provides a simple way to write unit tests with as little code as necessary. It consists of one header file and one static library.
- include/ contains all header files
- src/ contains the sources for the library as well as the selftest and template for test suites
- bin/ contains the executable selftest
- lib/ contains the built library
- temp/ contains temporary object files
Execute the make lib
command inside the project directory to compile the CUnit static library.
Use make run
to execute a selftest and use make clean
to delete all built files.
Add the include/cunit.h header file to the search path of your compiler and the lib/libcunit.a
static library to the search path of your linker. Copy the src/template.c
test suite template
to your project and rename it if you wish. Follow the instructions written there. For compilation
simply compile and link together all test files.
Each test suite should reside in a separate .c file and each test case function should be
declared as static
. This way no name collisions should occur. To call the test suite from a
main()
function it is adviced to have an alltests()
function inside the test suite. You can
then compile all your test suites together with a main()
function to create an executable test.
// inside template.c
#include "cunit.h"
static TestResult testCase() {
TestResult result = {};
apply(&result, assertEqualInt(42, 42));
TEST(assertEqualInt(42, 42)); // does exactly the same as the line above
return result;
}
TestResult template_alltests(PrintLevel verbosity) {
TestSuite suite = newSuite(__FILE__, "Test Suite Template");
addTest(&suite, &testCase);
TestResult result = run(&suite, verbosity);
deleteSuite(&suite);
return result;
}
// inside main.c
#include "cunit.h"
extern TestResult template_alltests(PrintLevel);
int main() {
TestResult result = {};
result = unite(result, template_alltests(VERBOSE));
printResult(result);
return 0;
}
By passing a PrintLevel
to alltests()
(which is propagated to run()
) of a test suite one
can apply the level of detail for the output. If the level is set to VERBOSE
then the test
suite will print the result of every assertion. By passing SPARSE
the summary of each test case
is printed. SUMMARY
will print only the result of each test suite. SILENT
will supress any
output. When developping on a new test suite the VERBOSE
level is suited best. Once all test
cases pass, one can set the level to SPARSE
or even to squash the output. If a test suite has
many test cases the output can be reduced with the SUMMARY
level even further. SILENT
is best
suited if one is only interested in the test result that is for instance used in some script.
CUnit comes with a bunch of assert functions.
bool assertFalse(bool cond);
bool assertTrue(bool cond);
bool assertEqualBool(bool value, bool expected);
bool assertNotEqualBool(bool value, bool expected);
bool assertEqualInt(int value, int expected);
bool assertNotEqualInt(int value, int expected);
bool assertEqualChar(char value, char expected);
bool assertNotEqualChar(char value, char expected);
bool assertEqualSize(size_t value, size_t expected);
bool assertNotEqualSize(size_t value, size_t expected);
bool assertEqualFloat(float value, float expected);
bool assertNotEqualFloat(float value, float expected);
bool assertEqualDouble(double value, double expected);
bool assertNotEqualDouble(double value, double expected);
bool assertEqualString(const char* string, const char* expected);
bool assertNotEqualString(const char* string, const char* expected);
bool assertNull(const void* pointer);
bool assertNotNull(const void* pointer);
bool assertSame(const void* pointer, const void* expected);
bool assertNotSame(const void* pointer, const void* expected);
bool assertEqualMemory(const void* pointer, const void* expected, size_t length);
If they don't suffice your needs you can simply write your own assert function.
#include "cunit.h"
#define assertEqual(val, exp) __assertEqual(__FILE__, __LINE__, val, exp)
bool __assertEqual(const char* file, int line, int value, int expected) {
printVerbose(__PROMPT, file, line);
if (value == expected) {
printVerbose(GRN "OK\n" RST);
return true;
} else {
printVerbose(RED "ERROR: " RST "expected [%d] == [%d]\n", value, expected);
return false;
}
}
To imitate the output behavior of CUnit it is adviced to use the provided printVerbose()
function
instead of printf()
.
For convinience and for better expressibility CUnit comes with a couple of macros to call your
assertions with. All you need to do is to declare a TestResult
variable.
The TEST()
macro is a simple wrapper around an assert function which will automatically store
the result in the TestResult
variable.
If you want to abort a test case on a strong precondition you can use the ABORT()
macro. It works
just like TEST()
but exits the surrounding function if the assertion failed.
You can skip a test with the SKIP()
macro. The INFO()
macro can be used to print a message,
whereas the FAIL()
will print a message and add a failed test to the result.
static TestResult example() {
TestResult result = {}; // NECESSARY, macros expect it, do NOT change
INFO("test with macros"); // simply prints a message
TEST(assertEqualInt(13, 42)); // result += assertEqualInt(13, 42);
SKIP(assertEqualInt(42, 42)); // result = result
FAIL("intended to fail"); // result += false
ABORT(assertEqualInt(42, 42)); // like TEST(assertEqualInt(42, 42)) BUT
ABORT(assertEqualInt(13, 42)); // will exit testWithMacros() if assertion fails
TEST(assertEqualInt(42, 42)); // won't be reached
return result; // result has 3 failed tests out of 4
}
All macros are effected by the verbosity level and are set to VERBOSE
. That cannot be modified.
The central point of CUnit is the TestSuite
structure that stores and executes all added test
cases. Results are collected in the TestResult
structure and propagated to the caller.
typedef TestResult (*TEST_FN)();
typedef enum PrintLevel {
SILENT, SUMMARY, SPARSE, VERBOSE
} PrintLevel;
typedef struct TestResult {
int failedTests;
int totalTests;
} TestResult;
typedef struct Test {
const char* name;
TEST_FN fn;
} Test;
typedef struct TestSuite {
const char* name;
const char* description;
int numTests;
Test* tests;
} TestSuite;
TestResult unite(TestResult a, TestResult b); // returns the united test results
void apply(TestResult* result, bool testResult); // applies a boolean to a result
void printResult(TestResult result); // print test result
TestSuite newSuite(const char* name, const char* description); // create new test suite
void deleteSuite(TestSuite* suite); // delete all added tests
void addTest(TestSuite* suite, TEST_FN fn); // adds a test case to a suite
TestResult run(const TestSuite* suite, PrintLevel verbosity); // executes all tests in a suite
void printVerbose(const char* format, ...); // print when level is set to VERBOSE or higher
void printSparse(const char* format, ...); // print if level is set to SPARSE or higher
void printSummary(const char* format, ...); // print if level is set to SUMMARY or higher
void printAlways(const char* format, ...); // ignore the level and print