Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
The test framework code is in a subdirectory called TestSuite
in the code distribution available at www.MindView.net. To use it, include the
search path for the TestSuite subdirectory in your header, link the
object files, and include the TestSuite subdirectory in the library
search path. Here is the header for Test.h:
//: TestSuite:Test.h
#ifndef TEST_H
#define TEST_H
#include <string>
#include <iostream>
#include <cassert>
using std::string;
using std::ostream;
using std::cout;
// fail_() has an underscore to prevent collision with
// ios::fail(). For consistency, test_() and succeed_()
// also have underscores.
#define test_(cond) \
do_test(cond, #cond, __FILE__, __LINE__)
#define fail_(str) \
do_fail(str, __FILE__, __LINE__)
namespace TestSuite {
class Test {
ostream* osptr;
long nPass;
long nFail;
// Disallowed:
Test(const Test&);
Test& operator=(const Test&);
protected:
void do_test(bool cond, const string& lbl,
const char* fname, long lineno);
void do_fail(const string& lbl,
const char* fname, long lineno);
public:
Test(ostream* osptr = &cout) {
this->osptr = osptr;
nPass = nFail = 0;
}
virtual ~Test() {}
virtual void run() = 0;
long getNumPassed() const { return nPass; }
long getNumFailed() const { return nFail; }
const ostream* getStream() const { return osptr; }
void setStream(ostream* osptr) { this->osptr =
osptr; }
void succeed_() { ++nPass; }
long report() const;
virtual void reset() { nPass = nFail = 0; }
};
} // namespace TestSuite
#endif // TEST_H ///:~
There are three virtual functions in the Test class:
A virtual destructor
The function reset( )
The pure virtual function run( )
As explained in Volume 1, it is an error to delete a derived
heap object through a base pointer unless the base class has a virtual
destructor. Any class intended to be a base class (usually evidenced by the
presence of at least one other virtual function) should have a virtual
destructor. The default implementation of the Test::reset( ) resets
the success and failure counters to zero. You might want to override this function
to reset the state of the data in your derived test object; just be sure to
call Test::reset( ) explicitly in your override so that the
counters are reset. The Test::run( ) member function is pure
virtual since you are required to override it in your derived class.
The test_( ) and fail_( ) macros can
include file name and line number information available from the preprocessor.
We originally omitted the trailing underscores in the names, but the fail( )
macro then collided with ios::fail( ), causing compiler errors.
Here is the implementation of the remainder of the Test
functions:
//: TestSuite:Test.cpp {O}
#include "Test.h"
#include <iostream>
#include <typeinfo>
using namespace std;
using namespace TestSuite;
void Test::do_test(bool cond, const std::string&
lbl,
const char* fname, long lineno) {
if(!cond)
do_fail(lbl, fname, lineno);
else
succeed_();
}
void Test::do_fail(const std::string& lbl,
const char* fname, long lineno) {
++nFail;
if(osptr) {
*osptr << typeid(*this).name()
<< "failure: (" << lbl
<< ") , " << fname
<< " (line " << lineno
<< ")" << endl;
}
}
long Test::report() const {
if(osptr) {
*osptr << "Test \"" <<
typeid(*this).name()
<< "\":\n\tPassed: "
<< nPass
<< "\tFailed: " <<
nFail
<< endl;
}
return nFail;
} ///:~
The Test class keeps track of the number of successes
and failures as well as the stream where you want Test::report( )
to display the results. The test_( ) and fail_( )
macros extract the current file name and line number information from the
preprocessor and pass the file name to do_test( ) and the line
number to do_fail( ), which do the actual work of displaying a
message and updating the appropriate counter. We can t think of a good reason
to allow copy and assignment of test objects, so we have disallowed these
operations by making their prototypes private and omitting their respective
function bodies.
Here is the header file for Suite:
//: TestSuite:Suite.h
#ifndef SUITE_H
#define SUITE_H
#include <vector>
#include <stdexcept>
#include "../TestSuite/Test.h"
using std::vector;
using std::logic_error;
namespace TestSuite {
class TestSuiteError : public logic_error {
public:
TestSuiteError(const string& s = "")
: logic_error(s) {}
};
class Suite {
string name;
ostream* osptr;
vector<Test*> tests;
void reset();
// Disallowed ops:
Suite(const Suite&);
Suite& operator=(const Suite&);
public:
Suite(const string& name, ostream* osptr =
&cout)
: name(name) { this->osptr = osptr; }
string getName() const { return name; }
long getNumPassed() const;
long getNumFailed() const;
const ostream* getStream() const { return osptr; }
void setStream(ostream* osptr) { this->osptr =
osptr; }
void addTest(Test* t) throw(TestSuiteError);
void addSuite(const Suite&);
void run(); // Calls Test::run() repeatedly
long report() const;
void free(); // Deletes tests
};
} // namespace TestSuite
#endif // SUITE_H ///:~
The Suite class holds pointers to its Test
objects in a vector. Notice the exception specification on the addTest( )
member function. When you add a test to a suite, Suite::addTest( )
verifies that the pointer you pass is not null; if it is null, it throws a TestSuiteError
exception. Since this makes it impossible to add a null pointer to a suite, addSuite( )
asserts this condition on each of its tests, as do the other functions that
traverse the vector of tests (see the following implementation). Copy
and assignment are disallowed as they are in the Test class.
//: TestSuite:Suite.cpp {O}
#include "Suite.h"
#include <iostream>
#include <cassert>
#include <cstddef>
using namespace std;
using namespace TestSuite;
void Suite::addTest(Test* t) throw(TestSuiteError) {
// Verify test is valid and has a stream:
if(t == 0)
throw TestSuiteError("Null test in
Suite::addTest");
else if(osptr && !t->getStream())
t->setStream(osptr);
tests.push_back(t);
t->reset();
}
void Suite::addSuite(const Suite& s) {
for(size_t i = 0; i <
s.tests.size(); ++i) {
assert(tests[i]);
addTest(s.tests[i]);
}
}
void Suite::free() {
for(size_t i = 0; i < tests.size(); ++i) {
delete tests[i];
tests[i] = 0;
}
}
void Suite::run() {
reset();
for(size_t i = 0; i < tests.size(); ++i) {
assert(tests[i]);
tests[i]->run();
}
}
long Suite::report() const {
if(osptr) {
long totFail = 0;
*osptr << "Suite \"" <<
name
<< "\"\n=======";
size_t i;
for(i = 0; i < name.size(); ++i)
*osptr << '=';
*osptr << "="
<< endl;
for(i = 0; i <
tests.size(); ++i) {
assert(tests[i]);
totFail += tests[i]->report();
}
*osptr << "=======";
for(i = 0; i < name.size(); ++i)
*osptr << '=';
*osptr << "="
<< endl;
return totFail;
}
else
return getNumFailed();
}
long Suite::getNumPassed() const {
long totPass = 0;
for(size_t i = 0; i < tests.size(); ++i) {
assert(tests[i]);
totPass += tests[i]->getNumPassed();
}
return totPass;
}
long Suite::getNumFailed() const {
long totFail = 0;
for(size_t i = 0; i < tests.size(); ++i) {
assert(tests[i]);
totFail += tests[i]->getNumFailed();
}
return totFail;
}
void Suite::reset() {
for(size_t i = 0; i < tests.size(); ++i) {
assert(tests[i]);
tests[i]->reset();
}
} ///:~
We will be using the TestSuite framework wherever it
applies throughout the rest of this book.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |