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:
- __FILE__ and __LINE__ format properly in the output from the asserts, so you can use F4 in MSVC to move to the next failed assert. It has to be formatted like "the_file_name_name(lineno) : ".
- OutputDebugString() pushes all diagnostics into your editor's output panel. This minimize the futzing at test time.
- the test cases support inheritance of common setUp and tearDown. A little more creativity would be required to support diamond inheritance, for Wiki:AbstractTests.
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