Tutorial

This tutorial provides you with a quick introduction to libunittest. Note that this tutorial reflects the state of the latest version of libunittest. Please also check out the examples and unit tests that are shipped with libunittest.

  • Assembling a test project
  • Random values and containers
  • Miscellaneous
  • Developer notes
  • Assembling a test project

    There are different ways of writing tests with libunittest depending on your needs and requirements. The easiest way to do it would be to define a source file like this: // easy.cpp
    #include <libunittest/all.hpp>

    TEST(test_value_is_true)
    {
    ASSERT_TRUE(true)
    }

    struct fixture {
    int value;
    fixture() : value(42) {}
    };

    TEST_FIXTURE(fixture, test_with_fixture)
    {
    ASSERT_EQUAL(42, value)
    }

    By including libunittest/all.hpp you get an automatic main function and access to all macros. The following three preprocessor switches let you disable certain features:

  • UNITTEST_NO_MAIN_FUNC - Define to not include an automatic main function
  • UNITTEST_NO_ASSERT_SHORT - Define to disable shorter assert macros
  • UNITTEST_NO_MACRO_SHORT - Define to disable shorter miscellaneous macros
  • Note that the above example uses the easy style by using the macros TEST and TEST_FIXTURE. However, for greater flexibility and faster compile times the alternative way of writing tests is called the explicit style because one has to write explicit test classes. This testing style is usually recommended for larger test projects. An example on how to do that can be found here.

    In order to compile the above example it is assumed that libunittest is installed in a default system location. In order to compile on Unix-like systems using g++(≥4.6), you can do: g++ -std=c++0x -pthread -lunittest easy.cpp -o unittest.exe Doing ./unittest.exe -v   gives the following output: test_value_is_true::test ... [0.000127s] ok
    test_with_fixture::test ... [0.000154s] ok

    --------------------------------------------------
    Ran 2 tests in 0.000281s

    OK
    To get a quick help to your test application just do ./unittest.exe -h: This is your test application using libunittest 9.3.1

    Usage: ./unittest.exe [Arguments]

    Arguments:
    -h             Displays this help message and exits
    -v             Sets verbose output for running tests
    -d             A dry run without actually executing any tests
    -s             Stops running tests after the first test fails
    -x             Enables the generation of the XML output
    -e             Turns off handling of unexpected exceptions
    -k             Runs tests regardless of whether they are skipped
    -i             Disables the measurement of any test timeouts
    -p number      Runs tests in parallel with a given number of threads
    -n name        A certain test to be run superseding any other run filter
    -f string      A run filter applied to the beginning of the test names
    -g regex       A run filter based on regex applied to the test names
    -z shuffseed   A seed of >= 0 enables shuffling (0 means time based)
    -t timeout     A timeout in seconds for tests without static timeouts
    -r precision   The maximum displayed value precision
    -l length      The maximum displayed string length (default: 500)
    -o xmlfile     The XML output file name (default: libunittest.xml)
    -u suite       The name of the test suite (default: libunittest)

    Random values and containers

    Using random functionality can be a convenient way of generating test data. libunittest provides easy-to-use classes to generate random values and containers. All random stuff can be found in libunittest/random.hpp. All random classes have a common abstract base class called random_object which has a simple public interface (in pseudo code):

  • get() - Returns a new random value or container at every call.
  • seed(value) - Let's you set a new random seed. The default seed is 1.
  • clone() - Returns a new, cloned instance of the current class.
  • Let's check out an example: auto random = unittest::make_random_value<int>(10);
    random->seed(42); // let's you set the random seed (default is 1)
    int rand_value1 = random->get(); // a random int in [0, 10]
    int rand_value2 = random->get(); // another random int in [0, 10]

    Here's an overview of the random classes (in pseudo code) with brief descriptions:

  • random_value<T> represents a random value where T can be something like int, double, or bool. Available constructors are: random_value() // value between 0 and 1
    random_value(maximum) // value between 0 and max
    random_value(minimum, maximum) // value between min and max
    The corresponding factory functions are: make_random_value<T>(...)
  • random_choice<Container> let's you pick a random element from a given container such as std::vector or std::list. The available constructor is: random_choice(container) The corresponding factory function is: make_random_choice(container)
  • random_container<Container> generates a random container from a given random object. Available constructors are: random_container(random_object, size)
    random_container(random_object, min_size, max_size)
    The corresponding factory functions are: make_random_container(...)
    make_random_vector(...) // for convenience
  • random_shuffle<Container> generates a shuffled version of a given container. Available constructors are: random_shuffle(container)
    random_shuffle(container, size)
    The corresponding factory functions are: make_random_shuffle(...)
  • random_tuple<Args...> generates a random tuple from given random objects. Available constructor is: random_tuple(random_objects...) The corresponding factory function is: make_random_tuple(random_objects...)
  • random_pair<F,S> generates a random pair from two random objects. Available constructor is: random_pair(random_object1, random_object2) The corresponding factory function is: make_random_pair(random_object1, random_object2)
  • random_combination<Container1, Container2> generates random pairs of two containers. Available constructor is: random_combination(container1, container2, size)
    The corresponding factory function is: make_random_combination(container1, container2, size)
  • Miscellaneous

  • You can make use of non-deadly assertions by simply pre-pending ND to the assertion, e.g., ASSERT_EQUAL(a, b) becomes NDASSERT_EQUAL(a, b) which means that execution will proceed even in case of an assertion failure.
  • You can run test cases in parallel by typing: ./test_application -p number where number denotes the number of concurrent threads to use.
  • There is a useful function, namely unittest::join(arg, args...), which joins an arbitrary number of arguments of arbitrary type to a single string.
  • The macro UNITTEST_TESTINFO (or shortcut TESTINFO) lets you log information about the current test. Just do something like TEST(test_number_greater_zero)
    {
    int number = 42;
    TESTINFO("Check for number greater than zero: ", number);
    ASSERT_GREATER(number, 0);
    }
    This will output the given test info to the output XML and to standard out in case of fails and errors. Note: Only use one TESTINFO macro per test and make sure it's at the beginning before stuff may go wrong.
  • You can find a collection of strings useful for testing under the namespace unittest::strings and defined in the file libunittest/strings.hpp.
  • Internal functionality important for the libunittest core but not relevant for most users can be found under the namespace unittest::core.
  • Developer notes

    If you'd like to contribute to libunittest then this section will be interesting for you. First you'd want to check out the code. For the following, it is assumed that you develop on a Unix-like OS such as Linux or MacOSX. However, developing on Windows and Visual Studio is just as easy.

    libunittest uses the GNU build system (autotools) which means you will need to have automake, autoconf, and libtool installed. In order to generate the necessary makefiles in debug mode just do: ./autogen.sh
    ./configure --enable-debug
    Now you can run things like make and make check.

    Whether you're developing a new feature or fixing a bug, please always and in any case make sure you test the stuff you're writing. You can easily add your tests to the existing test suite. Also consider providing an example on how to use your new feature. And finally, your code is only complete if it is properly documented. In order to check if your code documentation is complete you can do: make -C docs/doxygen Happy coding!