El código del framework de pruebas es un subdirectorio llamado TestSuite en la distribución de código disponible en www.MindView.net. Para usarlo, incluya la ruta de búsqueda para el subdirectorio TestSuite en la ruta de búsqueda de la biblioteca. Aquí está la cabecera para 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 ///:~
Listado 3.8.
Hay tres funciones virtuales en la clase Test:
Un destructor virtual
La función reset( )
La función virtual pura run( )
Como se explicó en el Volumen 1, es un error eliminar un objeto derivado de la pila a través de un puntero base a menos que la clase base tenga un destructor virtual. Cualquier clase propuesta para ser una clase base (normalmente evidenciadas por la presencia de al menos una de las otras funciones virtuales) tendría un destructor virtual. La implementación por defecto de Test::reset( ) pone los contadores de éxitos y fallos a cero. Podría querer sobreescribir esta función para poner el estado de los datos en su objeto de test derivado; sólo asegúrese de llamar a Test::rest( ) explícitamente en su sobreescritura de modo que los contadores se reajusten. La función Test::run( ) es virtual pura ya que es necesario para sobreescribirla en su clase derivada.
Las macros test_( ) y fail_( ) pueden incluir la información disponible del nombre del fichero y el número de línea del preprocesador. Originalmente omitimos el guión bajo en los nombres, pero la macro fail colisiona con ios::fail( ), provocando errores de compilación.
Aquí está la implementación del resto de las funciones Test:
//: 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; } ///:~
Listado 3.9.
La clase Test lleva la cuenta del número de éxitos y fracasos además de la cadena donde quiere que Test::report( ) muestre los resultados. Las macros test_( ) y fail_() extraen la información del nombre del fichero actual y el número de línea del preprocesador y pasa el nombre del fichero a do_test( ) y el número de línea a do_fail( ), que hacen el mismo trabajo de mostrar un mensaje y actualizar el contador apropiado. No podemos pensar una buena razón para permitir copiar y asignar objetos de prueba, por lo que hemos rechazado estas operaciones para hacer sus prototipos privados y omitir el cuerpo de sus respectivas funciones.
Aquí está el fichero de cabecera para 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 ///:~
Listado 3.10.
La clase Suite tiene punteros a sus objetos Test en un vector. Fíjese en la especificación de la excepción en la función addTest( ). Cuando añada una prueba a una suite, Suite::addTest( ) verifique que el puntero que pasa no sea null; si es null, se lanza una excepción TestSuiteError. Puesto que esto hace imposible añadir un puntero null a una suite, addSuite( ) afirma esta condición en cada prueba, como hacen las otras funciones que atraviesan el vector de pruebas (vea la siguiente implementación). Copiar y asignar están desestimados como están en la clase Test.
//: 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(); } } ///:~
Listado 3.11.
Usaremos el framework TestSuite donde sea pertinente a lo largo del resto de este libro.