tf-unit-test


unit testing framework for ansi c

A simple unit testing framework for C. Mostly inspired by check (http://check.sourceforge.net/) which unfortunately doesn't work on Windows.

Features

  • xUnit like testing macros
  • execute each test in it's own process
  • controllable timeouts
  • fixtures
  • setup / teardown at a fixture level
  • flexible

Why to run tests as a seprate processes?

Most of the unit testing frameworks for c/c++ share the same process for tests and the testing framework itself. This basic approach seems to work as long as a tested code doesn't crash in some unexpected way (like access violation). If such a crash occurs os terminates both faulty test and the testing framework at the same time. This means that no more tests from a suite will be executed, and in most cases system will exit with some cryptic error message. By running test in a separate process tested code is isolated from the framework. If tested code seg faults only hosting process is terminated. Testing framework can detect it as a failure and continue with running other tests.

The money test

Assume that we have a module for manipulating money: ``` /// Money representation. /// The currency and ammount fields are subject to change /// in future versions. Applications should use money apis /// to read / modify money state. typedef struct { const char* currency; double ammount; } money_t;

/// Initialize money_t structure with supplied values. void money_init(money_t* money, const char* currency, double ammount);

/// Obtain currency. const char* money_get_currency(money_t* money);

/// Obtain ammount. double money_get_ammount(money_t* money);

/// Add m1 and m2 and store result in dst. /// Dst may be the same pointer as m1 or m2. /// Return nonzero value on success. int money_add(money_t* dst, const money_t* m1, const money_t* m2);

/// Compare moneys. Follow C comparing convention (strcmp, memcmp) /// and return 0 if both objects are equal, nonzero value otherwise. int money_cmp(const money_t* m1, const money_t* m2); ```

Money fixture

First let's declare empty money test fixture that will hold all the test cases.

TF_BEGIN_FIXTURE(/tf/example/, money) { TF_SETUP(money_setup); TF_TEARDOWN(money_teardown); } TF_END_FIXTURE

The TF_BEGIN_FIXTURE macro has two arguments: path and fixture name. Paths are used to organize fixtures into filesystem like tree structure. As in other frameworks TF_SETUP / TF_TEARDOWN define a function that is executed before / after each test.

The money_setup() function setups three money objects: ``` static money_t money100Eur; static money_t money200Eur; static money_t money100Gbp;

static void money_setup() { money_init(&money100Eur, "EUR", 100.0); money_init(&money200Eur, "EUR", 200.0); money_init(&money100Gbp, "GBP", 100.0); } ```

First test

The next step is to write a test that checks if money_init() filled money objects with proper values:

``` TF_BEGIN_TEST(initializer_test) { money_t money;

TF_REQUIRE_EQ(money_get_ammount(&money100Eur), 100.0);
TF_REQUIRE_STR_EQ(money_get_currency(&money100Eur), "EUR");

TF_REQUIRE_EQ(money_get_ammount(&money200Eur), 200.0);
TF_REQUIRE_STR_EQ(money_get_currency(&money200Eur), "EUR");

TF_REQUIRE_EQ(money_get_ammount(&money100Gbp), 100.0);
TF_REQUIRE_STR_EQ(money_get_currency(&money100Gbp), "GBP");

} TF_END_TEST ```

Also we have to update fixture to include test: ``` TF_BEGIN_FIXTURE(/tf/example/, money) { TF_SETUP(money_setup); TF_TEARDOWN(money_teardown);

TF_TEST(initializer_test);

} TF_END_FIXTURE ```

Build

The last step is to build and run the test. The framework itself is contained in two .c source files. It's enough to add them the project along with the test sources. main() function is declared in tf-core.c. It parses the command line arguments and collects all the fixtures that has been declared inside project. Note that there's no need to manually register fixtures to the framework. TF_BEGIN/END_FIXTURE macros does that automatically.

Run

Running the executable should produce the following output: Running fixture '/tf/example/money' Running test: 'initializer_test' ok

The complete set of tests for the money example can be found in src_test\money.c.

Assertions

| TF_FAIL() | Unconditional fail. | |:-----------|:--------------------| | TF_REQUIRE(expression) | Require that the expression evaluates to true. | | TF_REQUIRE_EQ(exp1, exp2) | Require that exp1 == exp2. | | TF_REQUIRE_NEQ(exp1, exp2) | Require that exp1 != exp2. | | TF_REQUIRE_CLOSE(exp1, exp2, tolerance) | Require that fabs(exp1 - exp2) < tolerance.| | TF_REQUIRE_STR_EQ(str1, str2) | Require that both strings are equal. |

Run fixtures selectively

By default framework executes all the fixtures that has been declared inside project. You can pass paths to the fixtures as the command line arguments. For instance: * passing /tf/example/money would run only the money fixture from this tutorial * passing /tf/ as the argument would run all the fixtures declared in a project whose path starts with the '/tf/' prefix. It is not possible to run individual tests. As for now fixture is the smallest unit that can be executed.

Roadmap

  • support for unix like platforms
  • print test results as a xml

Project Information

The project was created on Jul 27, 2012.

Labels:
unittesting ansic c