the Compartmented Robust Posix C++ Unit Test system

Chapter 4. Fixtures for common test setups

It is not uncommon for several tests to work from a configuration that is common to all of them. Duplicating the same setup in each test is not only boring, it is a hallmark sign of poor software implementation, and also in itself error prone. A convenient way of doing away with the repetition is to use test fixtures.

A fixture is in itself nothing but a class (or struct,) which does all the configuration in the default constructor and, if necessary, does cleanup in the destructor. By adding the name of the fixture class (or struct) after the test name in the TEST(name, ...) definition, the fixture is inherited by the test class. Public or protected data and member functions in the fixture are available directly in the test body. It is possible to list several fixtures in the same TEST(name, ...) definition, in which case multiple inheritance is used. Fixtures and modifiers can be mixed in any order.

The simple symtable, below, was introduced on previous page on “Testsuites”:


     #include <map>
     #include <string>
     #include <cassert>
     
     class symtable
     {
     public:
       void add(const char *name, int val)
       {
         assert(name);
         table[name] = val;
       }
       int lookup(const char *name)
       {
         assert(name);
         return table.at(name);
       }
     private:
       std::map<std::string, int> table;
     };

Some of the tests can be rewritten with fixtures to avoid duplication:


     #include "symtable.hpp"
     #include <crpcut.hpp>
     
     class have_symtable
     {
     protected:
       symtable s;
     };
     
     class stdinsert : public have_symtable
     {
     protected:
       stdinsert()
       {
         s.add("one", 1);
         s.add("two", 2);
       }
     };
     
     class nullinsert : public have_symtable
     {
     protected:
       nullinsert()
       {
         s.add(0, 1);
       }
     };
     
     TEST(insert_and_lookup, stdinsert)
     {
       ASSERT_EQ(s.lookup("one"), 1);
       ASSERT_EQ(s.lookup("two"), 2);
     }
     
     TEST(lookup_nonexisting, stdinsert, EXPECT_EXCEPTION(std::out_of_range))
     {
       s.lookup("three");
     }
     
     TEST(add_null, nullinsert)
     {
     }
     
     TEST(lookup_null, stdinsert, EXPECT_SIGNAL_DEATH(SIGABRT), NO_CORE_FILE)
     {
       s.lookup(0);
     }
     
     int main(int argc, char *argv[])
     {
       return crpcut::run(argc, argv);
     }

Running this alternative test program yields:


     FAILED!: add_null
     stderr-------------------------------------------------------------------------
     symtable-test2: samples/symtable.hpp:36: void symtable::add(const char*, int): Assertion `name' failed.
     
     -------------------------------------------------------------------------------
     /tmp/crpcutu4ybWu/add_null is not empty!
     phase="creating"  -------------------------------------------------------------
     samples/symtable-test2.cpp:66
     Died with core dump
     -------------------------------------------------------------------------------
     ===============================================================================
     Files remain under /tmp/crpcutu4ybWu
     4 test cases selected
     
                    Sum   Critical   Non-critical
     PASSED   :       3          3              0
     FAILED   :       1          1              0

Something new here is the phase=creating on the error report for add_null. The phase can take the values:

creating

The error was detected when running the fixture constructors.

running

The error was detected when running the test function body.

destroying

The error was detected when running the fixture destructors.

post_mortem

The error was detected after the death of the test process.

child

The error was detected in a child process spawned from the test case. The entire process group is killed.

[Tip]Tip
Use fixtures templated on type to express parametrized tests and fixtures templated on references to globals to fake passing values to fixture constructors.

     
     #include <crpcut.hpp>
     #include <string>
     
     template <const char *(&str_param)>
     class fix
     {
     protected:
       fix() : string(str_param) {}
       std::string string;
     };
     
     void function_working_with_strings(const std::string &s)
     {
       INFO << s;
     }
     
     const char *hello = "Hello string fixture";
     const char *other = "Fresh out of cheddar";
     
     TEST(func_with_hello, fix<hello>)
     {
       function_working_with_strings(string);
     }
     
     TEST(func_with_other, fix<other>)
     {
       function_working_with_strings(string);
     }
     
     int main(int argc, char *argv[])
     {
       return crpcut::run(argc, argv);
     }

      
Running the program with the -v / --verbose command line flag yields:

     PASSED!: func_with_hello
     info---------------------------------------------------------------------------
     samples/string-template-fix.cpp:41
     Hello string fixture
     ===============================================================================
     PASSED!: func_with_other
     info---------------------------------------------------------------------------
     samples/string-template-fix.cpp:41
     Fresh out of cheddar
     ===============================================================================
     2 test cases selected
     
                    Sum   Critical   Non-critical
     PASSED   :       2          2              0