Unit Testing API

API Reference


Unit testing is an important aspect of a quantifiable quality assurance methodology. Although unit testing requires some extra overhead (the writing of the unit tests) during the development process it can provide enormous benefits during the project life cycle.

One benefit of writing unit tests is that it gets the developer using the interface to the unit they designed. This forces the developer to think about, and hopefully design for, usability of the interface early in the development cycle.

Another major benefit to unit testing is that it provides a documented and verifiable level of correctness for the designed unit. This allows the developer to refactor the code more aggressively and to quickly verify its correctness. Unit tests can also be used to perform regression testing when adding new features.

Despite the benefits of unit testing, unit tests are often omitted because of the initial overhead of writing the tests and the complexity of testing frameworks. Legato's Unit Test Framework is simple to use and very flexible and lightweight consisting of some handy macros.

Modes of Operation

The Legato Test Framework can run in one of two modes: pass through or exit on failure. In pass through mode all tests are run even if some of the tests fail. Failed tests are counted and reported on completion. Exit on failure mode also runs all tests but exits right away if any tests fail.

Mode selection is done through a command line argument. If the test program is run with the command line argument '-p' or '--pass-through' then pass through mode is selected. If the neither the '-p' or '--pass-through' arguments are present then exit on failure mode is selected.

Setting Up the Test Framework

To setup the Legato Test Framework, call the LE_TEST_INIT macro, once before any tests are started.

Performing Tests

To perform tests, call the LE_TEST macro and pass in your test function to LE_TEST as a parameter. The test function must have a bool return type which indicates that the test passed (true) or failed (false).

For example:

#include "legato.h"
 
// Returns true if the test passes, otherwise returns false.
bool Test1(void)
{
int expectedValue;
 
// Do some initializations and/or calculations.
...
 
// Call one of the unit-under-test's interface function and check it's return value against
// an expected value that was calculated earlier.
return (unitUnderTest_foo() == expectedValue);
}
 
 
// Returns true if the test passes, otherwise returns false.
bool Test2(void)
{
int expectedValue;
 
// Do some initializations and/or calculations.
...
 
// Call one of the unit-under-test's interface function and check it's return value against
// an expected value that was calculated earlier.
return (unitUnderTest_foo2() == expectedValue);
}
 
 
int main (void)
{
// Setup the Legato Test Framework.
 
// Run the tests.
LE_TEST(Test1());
LE_TEST(Test2());
 
// Exit with the number of failed tests as the exit code.
}

Exiting a Test Program

When a test program is finished executing tests and needs to exit, it should always exit using the LE_TEST_EXIT macro.

It's also okay to exit using LE_FATAL(), LE_FATAL_IF() or LE_ASSERT(), if the test must be halted immeditately due to some failure that cannot be recovered from.

Test Results

The LE_TEST_EXIT macro will cause the process to exit with the number of failed tests as the exit code.

Also, LE_TEST will log an error message if the test fails and will log an info message if the test passes.

If the unit test in the example above was run in "pass-through mode" (continue even when a test fails) and Test1 failed and Test2 passed, the logs will contain the messages:

 =ERR= | Unit Test Failed: 'Test1()'
  INFO | Unit Test Passed: 'Test2()'

And the return code would be 1.

Note
The log message format depends on the current log settings.

Multi-Threaded Tests

For unit tests that contain multiple threads run the various tests, the normal testing procedure will work because all the macros in this test framework are thread safe.

Multi-Process Tests

For unit tests that require the use of multiple concurrent processes, a single process can fork the other processes using LE_TEST_FORK() and then wait for them to terminate using LE_TEST_JOIN().

When a child that is being waited for terminates, LE_TEST_JOIN() will look at the child's termination status and add the results to the running test summary.

If the child process exits normally with a non-negative exit code, that exit code will be considered a count of the number of test failures that occurred in that child process.

If the child exits normally with a negative exit code or if the child is terminated due to a signal (which can be caused by a segmentation fault, etc.), LE_TEST_JOIN() will count one test failure for that child process.

{
// Setup the Legato Test Framework.
 
// Run the test programs.
le_test_ChildRef_t test1 = LE_TEST_FORK("test1");
le_test_ChildRef_t test2 = LE_TEST_FORK("test2");
 
// Wait for the test programs to finish and tally the results.
LE_TEST_JOIN(test1);
LE_TEST_JOIN(test2);
 
// Exit with the number of failed tests as the exit code.
}