Difference between revisions of "UnitTests"

From OPeNDAP Documentation
m
m (CppUnit basic layout)
Line 8: Line 8:
  
 
When writing tests, make the default behavior as quiet as possible since the tests will likely be run by an automated build. Use a debug macro or switch to control output of instrumentation.
 
When writing tests, make the default behavior as quiet as possible since the tests will likely be run by an automated build. Use a debug macro or switch to control output of instrumentation.
 +
 +
Here are the main sections of a CppUnit test:
 +
 +
==== Header files ====
 +
To get started, you need some header files so that you can subclass TestFixture and use the CppUnit macros.
 +
 +
<source lang="cpp">
 +
#include "config.h"
 +
 +
#include <cppunit/TextTestRunner.h>
 +
#include <cppunit/extensions/TestFactoryRegistry.h>
 +
#include <cppunit/extensions/HelperMacros.h>
 +
</source>
 +
 +
==== Subclassing TestFixture =====
 +
 +
==== Using macros ====
 +
 +
==== Main ====
 +
 +
==== Building the code ====
  
 
== Running tests by name ==
 
== Running tests by name ==

Revision as of 19:53, 6 May 2017

1 Writing Unit Tests for C++ using CppUnit

This page describes how to write unit tests for C++ classes using CppUnit. The basic process is fairly simple: a CppUnit::TestFixture is written that provides one or more tests for a specific class. However, there are several tricks to writing tests that will make them much easier to use. These help improve the interactive use of the tests, simplifying finding problems when the tests are run as part of an automated build, since these tests often become a kind of regression test once a class has been developed.

Some parts of these instructions are specific to OPeNDAP and its source code for libdap and the BES, but those parts are limited to code examples and it is easy to see how the same ideas could be applied to similar code that differs in its specifics.

1.1 CppUnit basic layout

The example below shows a complete unit test written with CppUnit. Using the helper macros streamlines writing the tests (but also introduces some wrinkles we discuss later on).

When writing tests, make the default behavior as quiet as possible since the tests will likely be run by an automated build. Use a debug macro or switch to control output of instrumentation.

Here are the main sections of a CppUnit test:

1.1.1 Header files

To get started, you need some header files so that you can subclass TestFixture and use the CppUnit macros.

#include "config.h"

#include <cppunit/TextTestRunner.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/extensions/HelperMacros.h>

1.1.2 Subclassing TestFixture =

1.1.3 Using macros

1.1.4 Main

1.1.5 Building the code

1.2 Running tests by name

To run a test by name, where 'test' means one of the methods in the TestFixture class, pass the method name to TestRunner::run(). For example, if you have a TestRunner named runner, a TestFixture class named MyTests and it has methods void test_1() {} and void test_2() {}, you would run those by calling runner.run("myTest::test_1") and runner.run(:myTes::test_2"). This will work if the test source file uses the macro CPPUNIT_TEST_SUITE( MyTest ); to declare the TestFixure and CPPUNIT_TEST(test_1);, ..., to declare the individual tests. Note that after the TestFixure class is defined, the macro CPPUNIT_TEST_SUITE_REGISTRATION(MyTest); must be used.

1.3 Using command line switches to control output

Look in the example to see how I use Gnu's GetOpt to process command line options. In the code, '-d' is used to turn on debugging. When on, a static global is set to true and a macro writes various instrumentation to stderr. Here's the global (file scope) and macro:

static bool debug = false;

#undef DBG
#define DBG(x) do { if (debug) (x); } while(false);

The #undef DBG is there because the #define will redefine the DBG macro, changing it from the definition provided by debug.h.

and the code to process the switch and call tests if they are named on the command line:

int main(int argc, char*argv[]) {
    CppUnit::TextTestRunner runner;
    runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());

    GetOpt getopt(argc, argv, "d");
    char option_char;
    while ((option_char = getopt()) != EOF)
        switch (option_char) {
        case 'd':
            debug = 1;  // debug is a static global
            break;
        default:
            break;
        }

    bool wasSuccessful = true;
    string test = "";
    int i = getopt.optind;
    if (i == argc) {
        // run them all
        wasSuccessful = runner.run("");
    }
    else {
        while (i < argc) {
            test = string("D4DimensionsTest::") + argv[i++];

            wasSuccessful = wasSuccessful && runner.run(test);
        }
    }

    return wasSuccessful ? 0 : 1;
}

The first loop processes the option; the if will call runner.run with "" which runs all the tests if there are no more arguments or will assume that remaining arguments are test names and will iterate over them, calling each in turn.


Note: The "-d" switch and calling the tests by name will not work with make. You have to run the test code from the command line by hand. If your unit test file is named testy.cc the you'll need to change directory to the unit-tests dir and do a ./testy -d testName to see the effects of these changes.

1.4 Putting it all together

#include "config.h"

#include <cppunit/TextTestRunner.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/extensions/HelperMacros.h>

#include <GetOpt.h> // Part of libdap

//#define DODS_DEBUG

#include "D4Dimensions.h"
#include "XMLWriter.h"

#include "Error.h"
#include "debug.h"

#include "testFile.h"
#include "test_config.h"

using namespace CppUnit;
using namespace std;
using namespace libdap;

static bool debug = false;

#undef DBG
#define DBG(x) do { if (debug) (x); } while(false);

class D4DimensionsTest: public TestFixture {
private:
    XMLWriter *xml;
    D4Dimensions *d;

public:
    D4DimensionsTest() {
    }

    ~D4DimensionsTest() {
    }

    void setUp() {
        d = new D4Dimensions;
        xml = new XMLWriter;
    }

    void tearDown() {
        delete xml;
        delete d;
    }

    void test_print_copy_ctor() {
        d->add_dim_nocopy(new D4Dimension("first", 10));
        d->add_dim_nocopy(new D4Dimension("second", 100));
        d->add_dim_nocopy(new D4Dimension("third"));

        D4Dimensions lhs(*d);

        lhs.print_dap4(*xml);
        string doc = xml->get_doc();
        string baseline = readTestBaseline(string(TEST_SRC_DIR) + "/D4-xml/D4Dimensions_3.xml");
        DBG(cerr << "test_print_copy_ctor: doc: " << doc << endl);
        DBG(cerr << "test_print_copy_ctor: baseline: " << baseline << endl);
        CPPUNIT_ASSERT(doc == baseline);
    }

    CPPUNIT_TEST_SUITE( D4DimensionsTest );

        CPPUNIT_TEST(test_print_empty);

        // CPPUNIT_TEST_EXCEPTION( test_error, Error );
 
    CPPUNIT_TEST_SUITE_END();
};

CPPUNIT_TEST_SUITE_REGISTRATION(D4DimensionsTest);

int main(int argc, char*argv[]) {
    CppUnit::TextTestRunner runner;
    runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());

    GetOpt getopt(argc, argv, "d");
    char option_char;

    while ((option_char = getopt()) != EOF)
        switch (option_char) {
        case 'd':
            debug = 1;  // debug is a static global
            break;
        default:
            break;
        }

    bool wasSuccessful = true;
    string test = "";
    int i = getopt.optind;
    if (i == argc) {
        // run them all
        wasSuccessful = runner.run("");
    }
    else {
        while (i < argc) {
            test = string("D4DimensionsTest::") + argv[i++];

            wasSuccessful = wasSuccessful && runner.run(test);
        }
    }

    return wasSuccessful ? 0 : 1;
}