private:
string name;
ostream* osptr;
vector<Test*> 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 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>
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 TestSuite framework wherever it applies throughout the rest of this book.
Debugging techniques
The best debugging habit to get into is to use assertions as explained in the beginning of this chapter; by doing so you’ll be more likely to find logic errors before they cause real trouble. This section contains some other tips and techniques that might help during debugging.
Trace macros
Sometimes it’s helpful to print the code of each statement as it is executed, either to cout or to a trace file. Here’s a preprocessor macro to accomplish this:.
#define TRACE(ARG) cout << #ARG << endl; ARG
Now you can go through and surround the statements you trace with this macro. Of course, it can introduce problems. For example, if you take the statement:.
for(int i = 0; i < 100; i++)
cout << i << endl;
and put both lines inside TRACE( ) macros, you get this:
TRACE(for(int i = 0; i < 100; i++))
TRACE( cout << i << endl;)
which expands to this:
cout << "for(int i = 0; i < 100; i++)" << endl;
for(int i = 0; i < 100; i++)
cout << "cout << i << endl;" << endl;
cout << i << endl;
which isn’t exactly what you want. Thus, you must use this technique carefully.
The following is a variation on the TRACE( ) macro:
#define D(a) cout << #a "=[" << a << "]" << '\n';
If you want to display an expression, you simply put it inside a call to D( ). The expression is displayed, followed by its value (assuming there’s an overloaded operator << for the result type). For example, you can say D(a + b). Thus, you can use this macro any time you want to test an intermediate value to make sure things are okay.
Of course, these two macros are actually just the two most fundamental things you do with a debugger: trace through the code execution and display values. A good debugger is an excellent productivity tool, but sometimes debuggers are not available, or it’s not convenient to use them. These techniques always work, regardless of the situation.
Trace file
DISCLAIMER: This section and the next contain code which is officially unsanctioned by the C++ standard. In particular, we redefine cout and new via macros, which can cause surprising results if you’re not careful. Our examples work on all the compilers we use, however, and provide useful information. This is the only place in this book where we will depart from the sanctity of standard-compliant coding practice. Use at your own risk!