test_(mybday != myevebday);
}
void testFunctions() {
test_(mybday.getYear() == 1951);
test_(mybday.getMonth() == 10);
test_(mybday.getDay() == 1);
test_(myevebday.getYear() == 1951);
test_(myevebday.getMonth() == 9);
test_(myevebday.getDay() == 30);
test_(mybday.toString() == "19511001");
test_(myevebday.toString() == "19510930");
}
void testDuration() {
Date d2(2003, 7, 4);
Date::Duration dur = duration(mybday, d2);
test_(dur.years == 51);
test_(dur.months == 9);
test_(dur.days == 3);
}
};
#endif ///:~
Running the test is a simple matter of instantiating a DateTest object and calling its run( ) member function.
//: C02:DateTest.cpp
// Automated Testing (with a Framework)
//{L} Date ../TestSuite/Test
#include <iostream>
#include "DateTest.h"
using namespace std;
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 Test class uses RTTI[23] 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 Test class 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] 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 Date constructors to throw a DateError exception (a type nested inside Date and 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.
Test suites
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] The Suite class allows you to collect tests into a functional unit. You derive Test objects to a Suite with 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 <iostream>
#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 Suite object. (As with Test, there is a second argument to the suite constructor that defaults to std::cout.) The destructor for Suite does not automatically delete the contained Test pointers because they don’t have to reside on the heap; that’s the job of Suite::free( ).
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.
24
In particular, we use
25
Batch files and shell scripts work well for this, of course. The Suite class is a C++-based way of organizing related tests.