int main() {
DateTest test;
test.run();
return test.report();
}
/* Output:
Test "DateTest":
Passed: 21, Failed: 0
*/ ///:~
The Test::report( )function displays the previous output and returns the number of failures, so it is suitable to use as a return value from main( ) .
The Testclass uses RTTI [23] “Runtime Type Identification”, discussed in chapter 9. Specifically, we use the name( ) member function of the typeinfo class. By the way, if you're using Microsoft Visual C++, you need to specify the compile option /GR . If you don't, you'll get an access violation at runtime.
to get the name of your class (for example, DateTest) for the report. There is also a setStream( )member function if you want the test results sent to a file instead of to the standard output (the default). You’ll see the Testclass implementation later in this chapter .
The test_ ( )macro can extract the text of the Boolean condition that fails, along with its file name and line number. [24] In particular, we use stringizing (via the # operator) and the predefined macros __FILE __ and __LINE__ . See the code later in the chapter.
To see what happens when a failure occurs, you can introduce an intentional error in the code, say by reversing the condition in the first call to test_( )in DateTest::testOps( )in the previous example code. The output indicates exactly what test was in error and where it happened: .
DateTest failure: (mybday > today) , DateTest.h (line 31)
Test "DateTest":
Passed: 20 Failed: 1
In addition to test_( ), the framework includes the functions succeed_( )and fail_( ), for cases in which a Boolean test won't do. These functions apply when the class you’re testing might throw exceptions. During testing, you want to arrange an input set that will cause the exception to occur to make sure it’s doing its job. If it doesn’t, it’s an error, in which case you call fail_( )explicitly to display a message and update the failure count. If it does throw the exception as expected, you call succeed_ ( )to update the success count .
To illustrate, suppose we update the specification of the two non-default Dateconstructors to throw a DateErrorexception (a type nested inside Dateand derived from std::logic_error) if the input parameters do not represent a valid date: .
Date(const string& s) throw(DateError);
Date(int year, int month, int day) throw(DateError);
The DateTest::run( )member function can now call the following function to test the exception handling:
void testExceptions() {
try {
Date d(0,0,0); // Invalid
fail_("Invalid date undetected in Date int ctor");
}
catch (Date::DateError&) {
succeed_();
}
try {
Date d(""); // Invalid
fail_("Invalid date undetected in Date string ctor");
}
catch (Date::DateError&) {
succeed_();
}
}
In both cases, if an exception is not thrown, it is an error. Notice that you have to manually pass a message to fail_( ), since no Boolean expression is being evaluated .
Real projects usually contain many classes, so you need a way to group tests so that you can just push a single button to test the entire project. [25] Batch files and shell scripts work well for this, of course. The Suite class is a C++-based way of organizing related tests.
The Suiteclass allows you to collect tests into a functional unit. You derive Testobjects to a Suitewith the addTest( )member function, or you can swallow an entire existing suite with addSuite( ). We have a number of date-related classes to illustrate how to use a test suite. Here's an actual test run: .
// Illustrates a suite of related tests
#include
#include "suite.h" // includes test.h
#include "JulianDateTest.h"
#include "JulianTimeTest.h"
#include "MonthInfoTest.h"
#include "DateTest.h"
#include "TimeTest.h"
using namespace std;
int main() {
Suite s("Date and Time Tests");
s.addTest(new MonthInfoTest);
s.addTest(new JulianDateTest);
s.addTest(new JulianTimeTest);
s.addTest(new DateTest);
s.addTest(new TimeTest);
s.run();
long nFail = s.report();
s.free();
return nFail;
}
/* Output:
Suite "Date and Time Tests"
===========================
Test "MonthInfoTest":
Passed: 18 Failed: 0
Test "JulianDateTest":
Passed: 36 Failed: 0
Test "JulianTimeTest":
Passed: 29 Failed: 0
Test "DateTest":
Passed: 57 Failed: 0
Test "TimeTest":
Passed: 84 Failed: 0
===========================
*/
Each of the five test files included as headers tests a unique date component. You must give the suite a name when you create it. The Suite::run( )member function calls Test::run( )for each of its contained tests. Much the same thing happens for Suite::report( ), except that it is possible to send the individual test reports to a destination stream that is different from that of the suite report. If the test passed to addSuite( )has a stream pointer assigned already, it keeps it. Otherwise, it gets its stream from the Suiteobject. (As with Test, there is a second argument to the suite constructor that defaults to std::cout.) The destructor for Suitedoes not automatically delete the contained Testpointers because they don’t have to reside on the heap; that’s the job of Suite::free( ) .
The test framework code library is in a subdirectory called TestSuitein the code distribution available on the MindView website. To use it, include the search path for the TestSuitesubdirectory in your header, link the object files, and include the TestSuitesubdirectory in the library search path. Here is the header for Test.h:
//: TestSuite:Test.h
#ifndef TEST_H
#define TEST_H
#include
#include
#include
using std::string;
using std::ostream;
using std::cout;
// The following have underscores because
// they are macros. For consistency,
// succeed_() also has an underscore.
#define test_(cond) \
do_test(cond, #cond, __FILE__, __LINE__)
#define fail_(str) \
do_fail(str, __FILE__, __LINE__)
namespace TestSuite {
class Test {
public:
Test(ostream* osptr = &cout);
virtual ~Test(){}
virtual void run() = 0;
long getNumPassed() const;
long getNumFailed() const;
const ostream* getStream() const;
void setStream(ostream* osptr);
void succeed_();
long report() const;
virtual void reset();
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);
private:
ostream* osptr;
long nPass;
long nFail;
// Disallowed:
Test(const Test&);
Test& operator=(const Test&);
};
inline Test::Test(ostream* osptr) {
this->osptr = osptr;
Читать дальше