Class Hierarchy Testing In Cpp Unit

BobBinder recently mentioned to me that it is a shame that CppUnit doesn't have some sort of support for automatically running the tests of superclasses on subclasses. It has tortured me a bit too, I often end up cutting and pasting test case functions from test class to test class. I think that in JavaUnit, you can just subclass the test class. In CppUnit it is more difficult because I use a pointer to a member function to dispatch the tests and you can't have those point to member functions in derived classes. I started to consider whether there might be a better way in CppUnit.

This is what I've ended up with. If anyone knows of any way to simplify this, please let me know.

Here is a tiny foolish hierarchy:

  class BoardGame
      virtual bool reset () { return true; }

class Chess : public BoardGame { public: virtual int numberOfPieces () { return 32; } };

And here is a test class for the BoardGame class:

  template<class GAMECLASS> class BoardGameTest : public TestCase
      GAMECLASS	*m_game;

public: BoardGameTest (string name) : TestCase (name) {}

BoardGameTest (TestSuite *suite) : TestCase ("") { suite->addTest (new TestCaller<BoardGameTest> ("testReset", testReset)); }

void setUp () { m_game = new GAMECLASS; }

void tearDown () { delete m_game; }

void testReset () { assert (m_game->reset ()); }


Notice that I'm using a template here, so I could instantiate with classes other than BoardGame, but I'm really only interested in subclasses at this point. The other notable thing is the fact that I'm abusing a constructor rather than using a static suite method on the class. The reason will be obvious in a second.

Here is a template class which tests the Chess class. Note that instances of it inherit BoardGameTest instantiated on the same class.

  template<class GAMECLASS> class ChessTest : public BoardGameTest<GAMECLASS>
      ChessTest (string name) : BoardGameTest<GAMECLASS> (name) 

ChessTest (TestSuite *suite) : BoardGameTest<GAMECLASS> (suite) { suite->addTest (new TestCaller<ChessTest> ("testNumberOfPieces", testNumberOfPieces)); }

void testNumberOfPieces () { assert (m_game->numberOfPieces () == 32); } };

Now we have one other little template class that helps us build up a suite. AutoSuite calls a constructor of a test class and uses it to fill itself with tests that can be run in the testrunner. Notice that the call through the constructor accepting TestSuite builds up a list of tests by traversing the inheritance tree.

  template<class TESTCASE> class AutoSuite : public TestSuite
      AutoSuite () 
      { TESTCASE testCase (this); }

So, now we can create the following object, which to the world looks like a TestSuite. It is a suite of tests which consists of all the tests that were defined in the ChessTest template and the BoardGameTest template, instantiated for Chess objects.

  AutoSuite<ChessTest<Chess> > chessTests;

Pop it into a testrunner:

  runner.addTest (&chessTests);

You can also use templates like these to verify that classes unrelated by inheritance conform to the same constraints.

Does anyone see any way to make this simpler? The constructor which accepts a suite does seem abusive, not the sort of thing you'd show your mother on mother's day, and it would not be bad to make another function which does this, but somehow it feels right like it is, even though it does not convey intention very well. I suppose I get concerned about forgetting to call the base, whereas it is rather automatic among C++ programmers to call base constructors with the same signature.

-- MichaelFeathers

I think you could get some additional benefits if you don't create the fixture for each TestCaller?, but reuse it between them:

  template <class Fixture> 
  class ReusingTestCaller : public TestCase
    REFERENCEOBJECT (ReusingTestCaller)
    typedef void             (Fixture::*TestMethod)();
    ReusingTestCaller (Fixture* fix, std::string name, TestMethod test)
      : TestCase (name), m_fixture (fix), m_test (test) {}
    void                    runTest () 
      { (m_fixture->*m_test)(); }  
    void                    setUp ()
      { m_fixture->setUp (); }
    void                    tearDown ()
      { m_fixture->tearDown (); }
    TestMethod               m_test;
    Fixture*   m_fixture;

In that case you could do something like this:

// Get rid of some verbosity #define ADD_TEST(theClass, thePointer, theMethod)addTest(new ReusingTestCaller<theClass>(thePointer, #theMethod, theMethod))

class ExampleTestCase : public TestCase { protected:
	double			m_value1;
	double			m_value2;
    ExampleTestCase (TestSuite *testSuite, std::string name) : TestCase (name) 
        testSuite->ADD_TEST(ExampleTestCase, this, anotherExample);
        testSuite->ADD_TEST(ExampleTestCase, this, testAdd);
        testSuite->ADD_TEST(ExampleTestCase, this, testDivideByZero);
        testSuite->ADD_TEST(ExampleTestCase, this, testEquals);
    void			setUp ();
    static Test		*suite ();
    void			example ();
    void			anotherExample ();
    void			testAdd ();
    void			testDivideByZero ();
    void			testEquals ();

class DerivedExampleTestCase : public ExampleTestCase { public:
    DerivedExampleTestCase(TestSuite *testSuite, std::string name) 
        :   ExampleTestCase(testSuite, name) 
	testSuite->ADD_TEST(DerivedExampleTestCase, this, testSilly);
        testSuite->ADD_TEST(DerivedExampleTestCase, this, testMostSilly);
    void			testSilly() 
        { bool silly = true; assert(!silly); }
    void			testMostSilly() 
        { bool silly = false; assert(silly); }


  TestSuite suite = new TestSuite();
  ExampleTestCase etc(suite, "ExampleTestCase");
  TestRunner  runner;
  runner.addTest (suite); ();    


  TestSuite suite = new TestSuite();
  DerivedExampleTestCase detc(suite, "DerivedExampleTestCase");
  TestRunner  runner;
  runner.addTest (suite); ();    

work as expected.

An advantage of this approach is that the base test case runs on a derived test case object if you are testing that derived object. I think this is in agreement with the "JavaUnit way". They don't need the "reuse fixture hack" though.

Hope this helps...

--- ErnestoGuisado

CategoryCpp CategoryTesting

View edit of April 23, 2012 or FindPage with title or text search