Algunas herramientas de pruebas unitarias automatizadas de C++ están disponibles en la World Wide Web para descargar, como CppUnit.[24] Nuestra intención aquí no es sólo presentar un mecanismo de prueba que sea fácil de usar, sino también fácil de entender internamente e incluso modificar si es necesario. Por lo tanto, en el espíritu de Hacer Lo Más Simple Que Podría Posiblemente Funcionar,[25] hemos desarrollado el Framework TestSuite, un espacio de nombres llamado TestSuite que contiene dos clases principales: Test y Suite.
La clase Test es una clase base abstracta de la cual deriva un objeto test. Tiene constancia del número de éxitos y fracasos y muestra el texto de cualquier condición de prueba que falla. Simplemente para sobreescribir la función run( ), que debería llamar en turnos a la macro test_() para cada condición de prueba boolean que defina.
Para definir una prueba para la clase Fecha usando el framework, puede heredar de Test como se muetra en el siguiente programa:
//: C02:DateTest.h #ifndef DATETEST_H #define DATETEST_H #include "Date.h" #include "../TestSuite/Test.h" class DateTest : public TestSuite::Test { Date mybday; Date today; Date myevebday; public: DateTest(): mybday(1951, 10, 1), myevebday("19510930") {} void run() { testOps(); testFunctions(); testDuration(); } void testOps() { test_(mybday < today); test_(mybday <= today); test_(mybday != today); test_(mybday == mybday); test_(mybday >= mybday); test_(mybday <= mybday); test_(myevebday < mybday); test_(mybday > myevebday); test_(mybday >= myevebday); 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 // DATETEST_H ///:~
Listado 3.5. C02/DateTest.h
Ejecutar la prueba es una sencilla cuestión de instaciación de un objeto DateTest y llamar a su función run( ):
//: 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 */ ///:~
Listado 3.6. C02/DateTest.cpp
La función Test::report( ) muestra la salida previa y devuelve el número de fallos, de este modo es conveniente usarlo como valor de retorno desde el main( ).
La clase Test usa RTTI[26] para obtener el nombre de su clase(por ejemplo, DateTest) para el informe. Hay también una función setStream() si quiere enviar los resultados de la prueba a un fichero en lugar de la salida estándar (por defecto). Verá la implementación de la clase Test más tarde en este capítulo.
La macro test_( ) puede extraer el texto de la condición booleana que falla, junto con el nombre del fichero y número de línea.[27] Para ver lo que ocurre cuando un fallo aparece, puede insertar un error intencionado en el código, por ejemplo invirtiendo la condición en la primera llamda a test_( ) en DateTest::testOps( ) en el código de ejemplo previo. La salida indica exactamente que la prueba tenía un error y dónde ocurrió:
DateTest fallo: (mybday > hoy) , DateTest.h (línea 31) Test "DateTest": Passados: 20 Fallados: 1
Además de test_( ), el framework incluye las funciones succed_( ) y fail_( ), para casos donde una prueba Boolean no funcionará. Estas funciones se aplican cuando la clase que está probando podría lanzar excepciones. Durante la prueba, crear un conjunto de entrada que causará que la excepción aparezca. Si no, es un error y puede llamar a fail_( ) explicitamente para mostrar un mensaje y actualizar el contador de fallos. Si lanza la excecpión como se esperaba, llame a succeed_( ) para actualizar el contador de éxitos.
Para ilustrar, suponga que modificamos la especificación de los dos constructor no por defecto de Date para lanzar una excepción DateError (un tipo anidado dentro de Date y derivado de std::logic_error) si los parámetros de entrada no representa un fecha válida: Date(const string& s) throw(DateError); Date(int year, int month, int day) throw(DateError);
La función DateTest::run( ) puede ahora llamar a la siguiente función para probar el manejo de excepciones:
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_(); } }
En ambos casos, si una excepción no se lanza, es un error. Fíjese que debe pasar manualmente un mensaje a fail_( ), pues no se está evaluando una expresión booleana.