home oakut

Fixtures - Discussion


JUnit uses the concept of fixtures. The idea behind fixture is that if there are more than one tests that need to operate on the same data or object, that object can be initialized once and used by all the tests. The data can be initialized in the setUp() method of the test class and once all the tests cases have been run, the framework calls the tearDown() method (possibly to release the resources).

Many C++ unit test frameworks based on JUnit follow the similar pattern of fixtures. While this pattern fits nicely in Java because of its architecture, the same perhaps cannot be said about C++ unit test frameworks. In the end, though, it is a matter of personal preference.

In the following paragraphs, I will talk about this concept and discuss various alternatives.

FIRST, AN EXAMPLE OF A FIXTURE

Let's consider a complex number class (class Complex). Below is an example of how a typical test class using fixtures can be written. It has two test cases: testAddition and testMultiplication. Both are the member functions of the class. setup() and tearDown() member functions set and clean up the fixture.


class ComplexNumberTest : public TestCase
{
private:

    Complex *pc1, *pc2, *pc3, *pc4;

public:

    void setUp()
    {
        pc1 = new Complex(100, 50);
        pc2 = new Complex(10, 30);
        pc3 = new Complex(110, 80);
        pc4 = new Complex(-500, 3500);
    }

    void tearDown()
    {
        delete pc4;  
        delete pc3;
        delete pc2;
        delete pc1;
    }

    void testAddition()
    {
        Complex c = *pc1 + *pc2;
        ASSERT_EQUAL(c, *pc3);
    }

    void testMultiplication()
    {
        Complex c = (*pc1) * (*pc2);
        ASSERT_EQUAL(c, *pc4);
    }
};

//...

CRITIQUE

C++ provides constructors and destructors to do the same job. Then, why do we need to write separate functions (setup/teardown)? For instance, the above code fragment could be rewritten as:


class ComplexNumberTest : public TestCase
{
private:

    Complex c1, c2, c3, c4;

public:

    ComplexNumberTest() : 
        c1(100, 50), c2(10, 30), c3(110, 80), c4(-500, 3500)
    {
    }

    void testAddition()
    {
        Complex c = c1 + c2;
        ASSERT_EQUAL(c, c3);
    }

    void testMultiplication()
    {
        Complex c = c1 * c2;
        ASSERT_EQUAL(c, c4);
    }
};

//...

Java does not have the concept of destructors. The best it can do to free the unused resources is to call the finalizers when the garbage collector reclaims the object.

Having such a design in Java makes it very easy for the user to add new test cases. To add a new test case, all the user has to do is to define a new method that starts with "test". JUnit uses Java's reflection API to discover all the test cases and run them. So user does not have to write new classes or write setup code to add new test cases. Write a new method in the test class and that automatically becomes a new test case. It is easy and elegent. In C++, in contrast, we have to let the framework know what our test cases are by making calls like these somewhere:

suite.addTest(&ComplexNumberTest::testAddition, ...);
suite.addTest(&ComplexNumberTest::testMultiplication, ...);
Here the effort shifts from defining new classes to making known to the framework the test cases that a class defines.

Another important point to consider is when failures or exceptions occur while initializing the data. Use of constructor/destructor may not be the best approach in this case. [One approach to use in this case could be to set a flag in the constructor and test for this flag in each test case. This already sounds cumbersome]. Having setup/teardown methods in such cases is beneficial.

APPROACH IN OAKUT