Skip to content

faltenberg/cunit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CUnit Test API

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.

Project Structure

  • 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

Building CUnit

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.

Adding CUnit to Your Project

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.

Writing a Test Suite

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;
}

Modifying Verbosity

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.

Writing Custom Assertions

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().

Using Macros

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.

Data Structures and Functions

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published