blob: 4963b760d0b70cf7acddfeb8834cd744148aa52f [file] [log] [blame]
CppUTest
========
This document is based on CppUTest v3.8. CppUtest is a unit testing framework
for testing C and C++ code. This document introduces the basic features of the
framework. For further information check the `official manual of CppUTest`_.
Why CppUTest?
-------------
First of all it was not our goal to develop a new unit testing framework while
plenty of open source solutions are already available. There were no special
requirements agains the unit testing framework that would rule out all existing
frameworks so we only had to choose a suitable one for our current and possible
future needs.
We ended up selecting CppUTest because of its small footprint and easy
portability. It also goes along with the standard xUnit frameworks' principles
and provides a standard interface to the outside world. Some details are listed
below.
- C/C++ support
- Small footprint (compared to Google Test)
- Easy to use on embedded systems
- Built-in mocking system (CppUMock)
- Implements four-phase testing pattern (setup, exercise, verify, teardown)
- Selective run of test cases
- Standard output format (JUnit, TeamCity)
Assertions
----------
Generally is a good practice to use more specific assertions because it can
output more informative error messages in case of a failure. The generic form or
assertions is ``assert(expected, actual)``. Each assert type has a _TEXT variant
for user defined error messages as last parameter.
- Boolean
- ``CHECK(condition)`` - Same as ``CHECK_TRUE``
- ``CHECK_TRUE(condition)``
- ``CHECK_FALSE(condition)``
- C string
- ``STRCMP_EQUAL(expected, actual)``
- ``STRNCMP_EQUAL(expected, actual, length)``
- ``STRCMP_NOCASE_EQUAL(expected, actual)``
- ``STRCMP_CONTAINS(expected, actual)``
- ``STRCMP_NOCASE_CONTAINS(expected, actual)``
- Integer
- ``LONGS_EQUAL(expected, actual)``
- ``UNSIGNED_LONGS_EQUAL(expected, actual)``
- ``LONGLONGS_EQUAL(expected, actual)``
- ``UNSIGNED_LONGLONGS_EQUAL(expected, actual)``
- ``BYTES_EQUAL(expected, actual)``
- ``SIGNED_BYTES_EQUAL(expected, actual)``
- ``POINTERS_EQUAL(expected, actual)``
- ``FUNCTIONPOINTERS_EQUAL(expected, actual)``
- Enums
- ``ENUMS_EQUAL_INT(expected, actual)``
- ``ENUMS_EQUAL_TYPE(underlying_type, expected, actual)``
- Other assertions
- ``CHECK_EQUAL(expected, actual)`` - Requires ``operator=`` and
``StringFrom(type)`` to be implemented
- ``CHECK_COMPARE(first, relop, second)`` - Same as ``CHECK_EQUAL`` but with
any type of compare
- ``DOUBLES_EQUAL(expected, actual, threshold)``
- ``MEMCMP_EQUAL(expected, actual, size)``
- ``BITS_EQUAL(expected, actual, mask)``
- ``FAIL()`` or ``FAIL_TEST()`` - Test case fails if called
- ``CHECK_THROWS(expected, expression)`` - Catching C++ exceptions
- Miscellaneous macros
- ``IGNORE_TEST`` - Same as ``TEST`` but it’s not called
- ``TEST_EXIT`` - Exists test
- ``UT_CRASH()`` - Crashes the test which is easy to catch with debugger
- ``UT_PRINT(text)`` - Generic print function
Test runner
-----------
Test cases are collected automatically. Under the hood the ``TEST`` macros are
creating global instances of classes and their constructor registers the test
cases into the test registry. This happens before entering the ``main``
function. In the ``main`` function only the ``RUN_ALL_TESTS`` macro needs to be
placed with the command line arguments passed to it. On executing the binary the
command line arguments will control the behaviour of the test process.
.. code-block:: C++
#include "CppUTest/CommandLineTestRunner.h"
int main(int argc, char* argv[]) {
return RUN_ALL_TESTS(argc, argv);
}
The default ``main`` implementation is added to all unit test suites by the
build system.
Command line options
--------------------
Command line options are available mainly for configuring the output format of
the test binary and for filtering test groups or cases.
- Output
- ``-v`` - Prints each test name before running them
- ``-c`` - Colorized output
- ``-o{normal, junit, teamcity}`` - Output format, junit can be processed by
most CIs
- ``-k packageName`` - Package name for junit output
- ``-lg`` - List test groups
- ``-ln`` - List test cases
- Other
- ``-p`` - Runs each test case in separate processes
- ``-ri`` - Runs ignored test cases
- ``-r#`` - Repeats testing ``#`` times
- ``-s seed`` - Shuffles tests
- Filtering test cases
- ``"TEST(groupName, testName)"`` - Running single test
- ``"IGNORE_TEST(groupName, testName)"`` -- Running single ignored test
- ``-g text`` - Runing groups containing text
- ``-n text`` - Runing tests containing text
- ``-sg text`` - Runing groups matching text
- ``-sn text`` - Runing tests matching text
- ``-xg text`` - Excluding groups containing text
- ``-xn text`` - Excluding tests containing text
- ``-xsg text`` - Excluding groups matching text
- ``-xsn text`` - Excluding tests matching text
Troubleshooting
---------------
Output messages
^^^^^^^^^^^^^^^
When one of tests fails the first step is to run it separately and check its
output message. Usually it shows the exact line of the file where the error
happened.
::
test_memcmp.cpp:17: error: Failure in TEST(memcmp, empty)
expected <1 0x1>
but was <0 0x0>
The executed tests can be followed by adding ``-v`` command line option.
::
./memcmp -v
TEST(memcmp, different) - 0 ms
TEST(memcmp, same) - 0 ms
TEST(memcmp, empty) - 0 ms
OK (3 tests, 3 ran, 1 checks, 0 ignored, 0 filtered out, 0 ms)
Catching failure with debugger
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If a failure happens in a helper function or in a loop where the assertion
is called multiple times it is harder to get the exact environment of a failure.
In this case it's a good practice to put a ``UT_CRASH()`` call into a
conditional block which hits when the failure happens. This way the debugger can
stop on failure because the code emits a signal.
.. code-block:: C++
TEST(magic, iterate) {
int result;
for(int i = 0; i < 1000; i++) {
result = magic_function(i);
// Debug code
if (result) {
UT_CRASH();
}
LONGS_EQUAL(0, result);
}
}
Using ``FAIL`` macro
^^^^^^^^^^^^^^^^^^^^
It's recommended to use ``FAIL`` macro in conditions that should never occur in
tests. For example if a test case loads test data from an external file but the
file could not be opened the ``FAIL`` macro should be used with an informative
message.
.. code-block:: C++
fd = open("test.bin", O_RDONLY);
if (fd < 0) {
FAIL("test.bin open failed");
}
Interference between test cases
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Test cases can interfere if there's a global resource which was not restored to
its original state after leaving a test case. This can be hard to find but at
least the it's easy to make sure that this is root case of an error. Let's
assume there's a global variable which is set during the test case but it
original value is not restore at the end. CppUTest has an command line option
for running each test case in separate process. This makes the global variable
to have its original value at the beginning of the test cases. Basically if the
test works by passing argument ``-p`` when running but fails without it, there's
a good chance for having an interference between test cases.
.. code-block:: C++
int x = 0;
TEST_GROUP(crosstalk) {
};
TEST(crosstalk, a) {
LONGS_EQUAL(0, x);
x = 1;
}
TEST(crosstalk, b) {
LONGS_EQUAL(0, x);
x = 1;
}
TEST(crosstalk, c) {
LONGS_EQUAL(0, x);
x = 1;
}
By running the test executable with different command line arguments it produces
a different result.
.. code-block::
./crosstalk -v
TEST(crosstalk, c) - 0 ms
TEST(crosstalk, b)
test_crosstalk.cpp:37: error:
Failure in TEST(crosstalk, b)
expected <0 0x0>
but was <1 0x1>
- 0 ms
TEST(crosstalk, a)
test_crosstalk.cpp:32: error: Failure in TEST(crosstalk, a)
expected <0 0x0>
but was <1 0x1>
- 0 ms
Errors (2 failures, 3 tests, 3 ran, 3 checks, 0 ignored, 0 filtered out, 0 ms)
./crosstalk -v -p
TEST(crosstalk, c) - 1 ms
TEST(crosstalk, b) - 0 ms
TEST(crosstalk, a) - 0 ms
OK (3 tests, 0 ran, 0 checks, 0 ignored, 0 filtered out, 2 ms)
--------------
*Copyright (c) 2019, Arm Limited. All rights reserved.*
.. _`official manual of CppUTest`: https://cpputest.github.io/