nPass = nFail = 0;
}
inline long Test::getNumPassed() const {
return nPass;
}
inline long Test::getNumFailed() const {
return nFail;
}
inline const ostream* Test::getStream() const {
return osptr;
}
inline void Test::setStream(ostream* osptr) {
this->osptr = osptr;
}
inline void Test::succeed_() {
++nPass;
}
inline void Test::reset() {
nPass = nFail = 0;
}
} // namespace TestSuite
#endif // TEST_H ///:~
There are three virtual functions in the Testclass:
· 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, of course, 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 original fail( )macro collided with ios::fail( ), causing all kinds of compiler errors .
Here is the implementation of Test:
//: TestSuite:Test.cpp {O}
#include "Test.h"
#include
#include // Note: Visual C++ requires /GR
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 << ")\n";
}
}
long Test::report() const {
if (osptr) {
*osptr << "Test \"" << typeid(*this).name()
<< "\":\n\tPassed: " << nPass
<< "\tFailed: " << nFail
<< endl;
}
return nFail;
} ///:~
No rocket science here. The Testclass just 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 "../TestSuite/Test.h"
#include
#include
using std::vector;
using std::logic_error;
namespace TestSuite {
class TestSuiteError : public logic_error {
public:
TestSuiteError(const string& s = "")
: logic_error(s) {}
};
class Suite {
public:
Suite(const string& name, ostream* osptr = &cout);
string getName() const;
long getNumPassed() const;
long getNumFailed() const;
const ostream* getStream() const;
void setStream(ostream* osptr);
void addTest(Test* t) throw (TestSuiteError);
void addSuite(const Suite&);
void run(); // Calls Test::run() repeatedly
long report() const;
void free(); // Deletes tests
private:
string name;
ostream* osptr;
vector tests;
void reset();
// Disallowed ops:
Suite(const Suite&);
Suite& operator=(const Suite&);
};
inline
Suite::Suite(const string& name, ostream* osptr)
: name(name) {
this->osptr = osptr;
}
inline string Suite::getName() const {
return name;
}
inline const ostream* Suite::getStream() const {
return osptr;
}
inline void Suite::setStream(ostream* osptr) {
this->osptr = osptr;
}
} // namespace TestSuite
#endif // SUITE_H ///:~
The Suiteclass holds pointers to its Testobjects 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 TestSuiteErrorexception. 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 Testclass .
//: TestSuite:Suite.cpp {O}
#include "Suite.h"
#include
#include
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 << "=\n";
for (i = 0; i < tests.size(); ++i) {
assert(tests[i]);
totFail += tests[i]->report();
}
*osptr << "=======";
for (i = 0; i < name.size(); ++i)
*osptr << '=';
*osptr << "=\n";
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 TestSuiteframework wherever it applies throughout the rest of this book .
Читать дальше