Nano Cpp Unit (Last Edit: Apr 30 2005 20:19:07)
RecentChanges Edit Search GoodStyle
Referenced By: RecursiveDescentParserCpp, RecursiveDescentParserTest, TfuiForMfc

This is not supposed to be a finished product, or something to answer all of your testing desires.

See http://www.gamesfromwithin.com/articles/0412/000061.html , "Exploring the C Unit Testing Framework Jungle", for a nice survey of all the tests.

This code demonstrates the minimum possible test rig that supports Wiki:TestDrivenDevelopment. It contains features which some of those other rigs in the survey get wrong. That's why there are so many of these rigs around!

This rig's root principle is to fails early and loud (not late and extensibly) if an assertion breaks. That requires these features:


The best way to use it is like this:

    struct
VectorSuite:  TestCase
{
  
    std::vector<std::string> aVector;

    void 
setUp()
    {
    aVector.push_back("alpha");
    aVector.push_back("beta");
    aVector.push_back("gamma");
    aVector.push_back("delta");
    }

    void 
tearDown()
    {
    aVector.clear();
    }
    
};

TEST_(VectorSuite, at)
{
    CPPUNIT_ASSERT_EQUAL(4, aVector.size());
    CPPUNIT_ASSERT_EQUAL("alpha", aVector.at(0));
    CPPUNIT_ASSERT_EQUAL("beta" , aVector.at(1));
    CPPUNIT_ASSERT_EQUAL("gamma", aVector.at(2));
    CPPUNIT_ASSERT_EQUAL("delta", aVector.at(3));
}    


TEST_(VectorSuite, indexOperator)
{
    CPPUNIT_ASSERT_EQUAL(4, aVector.size());
    CPPUNIT_ASSERT_EQUAL("alpha", aVector[0]);
    CPPUNIT_ASSERT_EQUAL("beta" , aVector[1]);
    CPPUNIT_ASSERT_EQUAL("gamma", aVector[2]);
    CPPUNIT_ASSERT_EQUAL("delta", aVector[3]);
}    


You declare a Test Suite, which is a common setUp and tearDown. Then you write any number of TEST_() invocations after that. This uses the Wiki:TestCollector pattern in C; the TEST_() macro itself uses stringerizing and token pasting to collect the test cases.

Assemble the following samples into debug.h, test.h, test.cpp and main.cpp, respectively:



debug.h:

#ifndef db

#   ifndef _DEBUG
#       error don't use this in release mode
#   endif

#   include <sstream>
#   include <iostream>
#   include <string>

    using std::cout;
    using std::wcout;

namespace none_of_your_business {

//  from http://www.codeproject.com/debug/debugout.asp

template <class CharT, class TraitsT = std::char_traits<CharT> >
class basic_debugbuf : 
    public std::basic_stringbuf<CharT, TraitsT>
{
public:

    virtual ~basic_debugbuf()
    {
        sync();
    }

protected:

    int sync()
    {
        output_debug_string(str().c_str());
        str(std::basic_string<CharT>());    // Clear the string buffer

        return 0;
    }

    void output_debug_string(const CharT *text) {}
};

template<>
void basic_debugbuf<char>::output_debug_string(const char *text)
{
    cout << str();
    ::OutputDebugStringA(text);
}

template<>
void basic_debugbuf<wchar_t>::output_debug_string(const wchar_t *text)
{
     wcout << str();
    ::OutputDebugStringW(text);
}


template<class CharT, class TraitsT = std::char_traits<CharT> >
class basic_dostream : 
    public std::basic_ostream<CharT, TraitsT>
{
public:

    basic_dostream() : std::basic_ostream<CharT, TraitsT>
                (new basic_debugbuf<CharT, TraitsT>()) {}
    ~basic_dostream() 
    {
        delete rdbuf(); 
    }
};

typedef basic_dostream<char>    dostream;
typedef basic_dostream<wchar_t> wdostream;

//  externs construct in any order, but statics construct top-to-bottom

    extern  dostream *p_dout;
    extern wdostream *p_wdout;

    static struct 
    oneShot {
    oneShot ()
        {
        if (!p_dout)   p_dout  = new  dostream;
        if (!p_wdout)  p_wdout = new wdostream;
        }} s_oneShot;

} // namespace none_of_your_business


static none_of_your_business:: dostream & dout  = *none_of_your_business::p_dout ;
static none_of_your_business::wdostream & wdout = *none_of_your_business::p_wdout;


#   define wdb(x)  do { wdout << WIDEN(__FILE__) << L"(" << __LINE__ << \
                             L") : " L#x L" = " << x << endl; \
                            } while (0)

  //  warning! x has no (latex) around it. Don't confuse
  //  it with operators that don't precede << 

#   define db(x)  do { dout << __FILE__ << "(" << __LINE__ << \
                             ") : " #x " = " << x << endl; \
                            } while (0)


#   ifdef USES_CONVERSION
    inline
    std::ostream& 
operator<<(std::ostream& os, const GUID& guid)
{

    OLECHAR sbuff[100];
    StringFromGUID2(guid, sbuff, 100);
    OutputDebugStringW(sbuff);
    USES_CONVERSION;
    os << W2A(sbuff);
    return os;
}
#   endif


#   if defined(WM_QUIT)  //  only members of the <windows.h> club may use this
    static void
DoEvents()
{
  MSG msg;

  while( GetMessage( &msg, NULL, 0, 0) )
  {     
    if (msg.message == WM_QUIT)
        return ;

    TranslateMessage( &msg );
    DispatchMessage( &msg );
  }
}
#   endif


    inline
    std::string
getCompiledPath_(std::string path)
    {
    std::string::size_type at (path.rfind('\\'));
//        ATLASSERT(at != path.npos);
    return path.substr(0, at + 1);  //  includes the trailing "\\"
    }
#define getCompiledPath() getCompiledPath_(__FILE__)

#define CHANGE_CURRENT_DIRECTORY_TO_COMPILED_PATH()  \
    namespace { static struct ccdtcp_ { ccdtcp_() {               \
        std::string folder (getCompiledPath());        \
        _chdir(folder.c_str());                          \
        }} ccdtcp_now_;  }  //  who wants the name ccdtpc_ anyway? ;-)


#endif




test.h:

#ifndef TEST_H_
#   define TEST_H_


  //  a lite test rig


#   include "debug.h"  //  for the wrappers on OutputDebugString
#   include <cmath>

#   define VC_EXTRALEAN
#   define WIN32_LEAN_AND_MEAN 

#include <windows.h>

    using std::endl;
    
                                            
#define SQUEAK_()                \
         do {  worked = false;   \
            if (stopAtError)  __asm { int 3 };  \
                } while(false)


//  TODO  oaoo with db?

#define CPPUNIT_SQUEAK(q)       \
         do {  dout << __FILE__ << '(' << __LINE__ << ") : Failed; " << q << endl;  \
            SQUEAK_();                               \
            } while (false)

#ifndef WIDEN
#   define WIDEN2(x) L##x
#   define WIDEN(x) WIDEN2(x)
#endif //! WIDEN


#define CPPUNIT_SQUEAKW(q)       \
         {  wdout << WIDEN(__FILE__) << L'(' << __LINE__ << L") : Failed; " << q << endl;  \
            SQUEAK_();                                \
            }


class disabled_test: public std::invalid_argument
{
public:
disabled_test(): std::invalid_argument("disabled_test")  {}

virtual char const * what() const 
    {
    return "disabled_test";  //  nobody uses this yet
    };
};

#define CPPUNIT_DISABLE(why) do { db(why);  throw disabled_test(); } while(false)

#define CPPUNIT_ASSERT(q)             \
            if (!(q))                 \
                CPPUNIT_SQUEAK(#q);


#define CPPUNIT_ASSERT_DOUBLES_EQUAL(alpha, omega, epsilon)      \
            if (fabs(alpha - omega) > epsilon)                   \
                CPPUNIT_SQUEAK("fabs(" << alpha << " - " << (omega) << ") > " << (epsilon));

    	
#define CPPUNIT_ASSERT_EQUAL(alpha, omega)            \
            if ((alpha) != (omega))                   \
                CPPUNIT_SQUEAK(#alpha " != " #omega << "; " << (alpha) << " != " << (omega));

#define CPPUNIT_ASSERT_GREATER(alpha, omega)            \
            if ((alpha) <= (omega))                   \
                CPPUNIT_SQUEAK(#alpha " <= " #omega << "; " << (alpha) << " <= " << (omega));

//  observe this only matches simple expressions

bool MatchesRegEx(std::string inputString, std::string regEx);

//  TODO  OAOO the several similar CPPUNIT_SQUEAK() calls

#define CPPUNIT_ASSERT_MATCH(regex, sample)            \
            if (!MatchesRegEx((sample), (regex)))                   \
                CPPUNIT_SQUEAK(#regex " !~ " #sample << ";\n" << (regex) << " !~ " << (sample));


#define CPPUNIT_ASSERT_NO_MATCH(regex, sample)            \
            if (MatchesRegEx((sample), (regex)))                   \
                CPPUNIT_SQUEAK(#regex " =~ " #sample << ";\n" << (regex) << " =~ " << (sample));


#define CPPUNIT_ASSERT_NOT_EQUAL(alpha, omega)            \
            if ((alpha) == (omega))                   \
                CPPUNIT_SQUEAK(#alpha " == " #omega << "; " << (alpha) << " == " << (omega));


#define CPPUNIT_ASSERT_EQUALW(alpha, omega)            \
            if ((alpha) != (omega))                   \
                CPPUNIT_SQUEAKW(L#alpha L" != " L#omega << L"; " << (alpha) << L" != " << (omega));


#define ATTENTION_ " -------------------------> "

#define INFORM(inform) \
            do { dout << inform << endl;  \
            } while(false)

#define CPPUNIT_TEST_SUITE(TestCase)    \
		    bool runTests() {            \
                worked = true;            \
                INFORM(#TestCase);         \
                classSetup();


#define CPPUNIT_TEST(callMe)       \
            INFORM("   " #callMe);  \
            try {                    \
             setUp();                 \
             (callMe)();               \
             }                          \
            catch (disabled_test & )     \
                {                         \
                INFORM("DISABLED");        \
                }                           \
            tearDown();                      
          //  The ExecuteAroundDesignPattern in a macro ;-)
          //  (but notice tearDown is forbidden to throw)


#define CPPUNIT_TEST_SUITE_END() \
            classTeardown();      \
		    return worked;		   \
		    }


    class
TestCase
    {
    public:
        static bool stopAtError;
        
        TestCase ():  worked (true), next(NULL) {
            next = head;
            head = this;            
            }

      	void			setUp () {}
  	    void			tearDown () {}
        bool worked;  //  TODO  make me static
        static TestCase *head;  //  TODO  make us private
        TestCase *next;
        virtual bool runTests() = 0;
        virtual void classSetup()  {}
        virtual void classTeardown()  {}

    };

    bool runAllTests();

#define TEST_(suite, target)            \
    struct suite##target:  public suite \
    {     void runCase();               \
    bool runTests() {  setUp();         \
        runCase();  tearDown();         \
        return worked; }                \
    }                                   \
    a##suite##target;                   \
    void suite##target::runCase()

#endif // ! TEST_H_



test.cpp:



//  a lite test rig


#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "test.h"  //  put our own header first over the implementation
#include <cassert>
#include <memory>

none_of_your_business:: dostream *none_of_your_business::p_dout;
none_of_your_business::wdostream *none_of_your_business::p_wdout;

TestCase *TestCase::head = NULL;


    class
ExampleTestCase:  public TestCase
{

public:
        ExampleTestCase():  setMeUp(false)
        {}

        void			setUp ();
        void            classSetup()
        {
                setMeUp = true;
        }

        CPPUNIT_TEST_SUITE( ExampleTestCase );
            CPPUNIT_TEST( test_example );
            CPPUNIT_TEST( test_anotherExample );
            CPPUNIT_TEST( test_Equals );
        CPPUNIT_TEST_SUITE_END();

private:
        double			m_value1;
        double			m_value2;
        bool            setMeUp;
        void			test_example ();
        void			test_anotherExample ();
        void			test_Equals ();

};



//    CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ExampleTestCase, "ExampleTestCase" );

void ExampleTestCase::setUp ()
{
        m_value1 = 2.0;
        m_value2 = 3.0;
}


void ExampleTestCase::test_example ()
{
        CPPUNIT_ASSERT (1 == 1);
        CPPUNIT_ASSERT (setMeUp);
}


void ExampleTestCase::test_anotherExample ()
{
        assert (2 == 2);  //  this causes instant test rig death
}


void ExampleTestCase::test_Equals ()
{
        std::auto_ptr<long>	l1 (new long (12));
        std::auto_ptr<long>	l2 (new long (12));

        CPPUNIT_ASSERT_DOUBLES_EQUAL (m_value1, 2.0, 0.01);
        CPPUNIT_ASSERT_DOUBLES_EQUAL (m_value2, 3.0, 0.01);
        CPPUNIT_ASSERT_EQUAL (12, 12);
        CPPUNIT_ASSERT_EQUAL (12L, 12L);
        CPPUNIT_ASSERT_EQUAL (*l1, *l2);

        assert (12L == 12L);
        CPPUNIT_ASSERT_DOUBLES_EQUAL (12.0, 11.99, 0.5);

}

#ifndef _SYSTRAN_TOOL_BAR_OCX

//  don't run the sample tests if we are inside SystranTP

static ExampleTestCase anExampleTestCase;

#endif

bool TestCase::stopAtError (true);


bool
runAllTests()
{

        TestCase *aCase = TestCase::head;
        bool result (true);

        for (; result && aCase; aCase = aCase -> next)
                result = aCase -> runTests();

        if (!result)  //  TODO  report disabled test count
                INFORM("Tests failed!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        else
                INFORM("All tests passed!");

        return result;  //  pass failure to calling script

}


//  this is a tiny regex matcher lifted from:

// http://twistedmatrix.com/users/jh.twistd/cpp/moin.cgi/SmallestSimpleRegexMatchCompetition

// MH - MatchesRegEx helper function. I decided to abbreviate its name to keep the scroll down.
//      Especially, since this function likes to play with itself, a lot.
static
bool
MH(const char *inputString, const char *regEx, bool plusAsStar=false)
{

        // For each remaining character in the current sub-string and each remaining reg-ex character
        for (
                bool escape = false;
                *inputString || *regEx ;
                inputString++, regEx++
        ) {
                if (*regEx=='\\') {
                        escape = true;
                        if (!*++regEx)
                                return false;
                }
                if (!escape)
                        switch (*regEx) {
                        case '?': // Match zero or one characters
                                if (MH(inputString,regEx+1)) // Try ignoring the ? reg ex char and matching the rest of the reg-ex (matching zero chars case)
                                        return true;
                                // Continue processing as if '?' was '.'
                        case '.': // Match any one character
                                if (!*inputString) // If no more chars left in string, not a match
                                        return false;
                                break; // Skip current char
                        case '*': // Match zero or more characters
                                return MH(inputString,regEx+1) || // Try moving on to the next reg-ex char
                                       *inputString && (MH(inputString+1,regEx) || // Try skipping the current string char
                                                        MH(inputString+1,regEx+1)); // Try doing both
                                break;
                        case '+': // Match one or more characters
                                return *inputString && MH(inputString+1,regEx,true) || // Skip a single character, treating the + in the reg-ex as a *
                                       plusAsStar && (MH(inputString,regEx+1) || // Try moving on to the next reg-ex char
                                                      *inputString && MH(inputString+1,regEx+1)); // Try skipping the current string char and the + treated as * in the reg-ex
                                break;
                        default: // Match the next character, exactly
                                if (*inputString!=*regEx) // Test for "out of reg-ex" or a mismatch, if the reg-ex char is not special
                                        return false;
                        }
                else // Current char is escaped
                {
                        escape=false; // Turn off escape
                        if (*inputString!=*regEx) // Test for "out of reg-ex" (which means the reg-ex is invalid) or a mismatch
                                return false;
                }
        }

        return !*inputString; // No more characters in the inputString indicates success

}


bool
MatchesRegEx(std::string inputString, std::string regEx)
{
        return MH(inputString.c_str(),regEx.c_str());
}




main.cpp:



   //  test rigs


#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma warning(disable: 4786)  //: identifier was truncated to '255' characters in the browser information
#include <vector>
#include "test.h"  //  put our own header first over the implementation


    class
vector_Tests:  public TestCase
{

  public:
    CPPUNIT_TEST_SUITE(vector_Tests);
        CPPUNIT_TEST(test_at);
        CPPUNIT_TEST(test_indexOperator);
        CPPUNIT_TEST(test_match);
    CPPUNIT_TEST_SUITE_END();

    std::vector<std::string> aVector;

    void 
setUp()
    {
    aVector.push_back("alpha");
    aVector.push_back("beta");
    aVector.push_back("gamma");
    aVector.push_back("delta");
    }

    void 
test_at()
    {
    CPPUNIT_ASSERT_EQUAL(4, aVector.size());
    CPPUNIT_ASSERT_EQUAL("alpha", aVector.at(0));
    CPPUNIT_ASSERT_EQUAL("beta" , aVector.at(1));
    CPPUNIT_ASSERT_EQUAL("gamma", aVector.at(2));
    CPPUNIT_ASSERT_EQUAL("delta", aVector.at(3));
    }

    void 
test_indexOperator()
    {
    CPPUNIT_ASSERT_EQUAL(4, aVector.size());
    CPPUNIT_ASSERT_EQUAL("alpha", aVector[0]);
    CPPUNIT_ASSERT_EQUAL("beta" , aVector[1]);
    CPPUNIT_ASSERT_EQUAL("gamma", aVector[2]);
    CPPUNIT_ASSERT_EQUAL("delta", aVector[3]);
    }

    void
test_match()
    {
    CPPUNIT_ASSERT_MATCH("*ta", aVector[3]);
    }

    void 
tearDown()
    {
    aVector.clear();
    }

};

static vector_Tests aVector_Tests;

TEST_(TestCase, obvious)
{
    CPPUNIT_ASSERT_EQUAL(7, 4 + 3);
}    

    struct
VectorSuite:  TestCase
{
  
    std::vector<std::string> aVector;

    void 
setUp()
    {
    aVector.push_back("alpha");
    aVector.push_back("beta");
    aVector.push_back("gamma");
    aVector.push_back("delta");
    }

    void 
tearDown()
    {
    aVector.clear();
    }
    
};

TEST_(VectorSuite, at)
{
    CPPUNIT_ASSERT_EQUAL(4, aVector.size());
    CPPUNIT_ASSERT_EQUAL("alpha", aVector.at(0));
    CPPUNIT_ASSERT_EQUAL("beta" , aVector.at(1));
    CPPUNIT_ASSERT_EQUAL("gamma", aVector.at(2));
    CPPUNIT_ASSERT_EQUAL("delta", aVector.at(3));
}    


TEST_(VectorSuite, indexOperator)
{
    CPPUNIT_ASSERT_EQUAL(4, aVector.size());
    CPPUNIT_ASSERT_EQUAL("alpha", aVector[0]);
    CPPUNIT_ASSERT_EQUAL("beta" , aVector[1]);
    CPPUNIT_ASSERT_EQUAL("gamma", aVector[2]);
    CPPUNIT_ASSERT_EQUAL("delta", aVector[3]);
}    


    int
main( int argc, char* argv[] )
{

    bool result (runAllTests());
    return int(!result);  //  pass failure to calling script

}




See EmbeddedCppUnit for a small cppunit that doesn't use a manual test suite

See also Wiki:CeePlusPlus