Programación defensiva en C++.
post está basado en el libro "Thinking in C++ - Volume Two" de Bruce Eckel y Chuck Allison.
A medida que un programa va creciendo en tamaño y complejidad, se hace cada vez más difícil rastrear los errores a través del código. Peor todavía con los errores que no saltan a simple vista.
Una de las ventajas de la programación modular es que, al dividir un problema en muchos problemas mas pequeños, si cada parte funciona bien, todo funciona bien. Y es más fácil hacer que una pequeña parte funcione bien, cada vez que se diseña una, que diseñar todo un programa enorme y tratar de que funcione bien.
Entonces vamos a ver algunas técnicas para ir asegurándonos que cada parte de nuestro programa funcione bien a medida que lo vamos haciendo. Así, cuando unamos todas las partes, el programa entero va a funcionar bien.
Aserciones.
Un algoritmo es, entre otras cosas, una expresión de nuestro intento por resolver un problema. Éste debería dejar en claro al lector (inclusive el escritor mismo) exactamente qué se estaba pensando cuando se diseñó esa porción de código así. En ciertos puntos del programa, se deberían poder hacer ciertas aserciones o aseveraciones.
Algunas ventajas[1]:
- Hace que, lo que de otra forma hubieras puesto como comentario, sea verificado en tiempo de ejecución para ver si realmente es así.
- Te permite expresar en el código lo que vos considerás como verdadero en un cierto punto de ejecución.
- Si no se cumple inmediatamente termina el programa.
- Probablemente ya hayas escrito comentarios en tu código. ¿Porqué no convertirlo en aserciones y hacer tu código más robusto?
- Podés estar seguro de que, si el código llegó ahí, todo lo que habías asumido como verdadero hasta ese punto se cumplió.
- No hacen, en absoluto, el código más lento (una vez compilado en release). Sólo existen para compilaciones "debug". Para "release" desaparecen.
Un programa sin aserciones se vería así:
#include <iostream> #include <algorithm> int main() { int v[]={0,1,2,3,4,5,6,7,8,9}; int tam=sizeof v / sizeof *v; int min=*std::min_element(v,v+tam); int max=*std::max_element(v,v+tam); std::cout<<"Min: "<<min<<std::endl; std::cout<<"Max: "<<max; return 0; }
Uno con aserciones sería:
#include <iostream> #include <algorithm> #include <cassert> int main() { int v[]={0,1,2,3,4,5,6,7,8,9}; int tam=sizeof v / sizeof *v; assert(tam==10); int min=*std::min_element(v,v+tam); assert(min==0); int max=*std::max_element(v,v+tam); assert(max==9); std::cout<<"Min: "<<min<<std::endl; std::cout<<"Max: "<<max; return 0; }
Cuando escribí este ejemplo, me confundí en varias cosas. Por ejemplo, al principio supuse "sizeof v" como 10, cuando en realidad era 40 ya que cada int me ocupaba 4 bytes. Saltó el assert.
También puse "assert(max==10)". Otra vez saltó, porque distraído no me dí cuenta que aunque el tamaño era 10, el mayor elemento era 9.
Como verás, es muy útil para ayudar al programador. Llená tu código de aserciones. Por ejemplo antes de evaluar la raiz cuadrada de un número, verificá que sea >=0. O antes de dividir por x verificá que "assert(x!=0). Te va a ahorrar dolores de cabeza.
La sintaxis de assert es:
void assert (int expression);
Está definida en la cabecera "<cassert>".
Para la versión final de tu programa, luego de haber probado que anda todo bien, eliminá automáticamente todas aserciones definiendo, antes de la inclusión de <cassert>, lo siguiente:
#define NDEBUG
La definición de assert, en <cassert> es equivalente a esta:
#ifdef NDEBUG #define assert(cond) ((void)0) #else void assertImpl(const char*, const char*,long); #define assert(cond)\ ((cond)?(void)0:assertImpl(???)) #endif
Estructura Automatizada de pruebas (Automated Test Framework).
Muchas veces los programadores, luego de haber construido una función, la probamos llamándola con algunos valores y fijándonos que nos retorna.
Esto es tedioso y muy propenso a errores. Las máquinas lo hacen mejor que los humanos. Entonces ¿porqué no automatizar el proceso?
El libro Thinking In C++ - Volume 2: Practical Programming presenta "la unidad de test automatizado más simple que pueda funcionar".
El código lo podés descargar de acá o copiarlo del final del post.
Uso[2]:
A continuación pongo como un ejemplo un programa que pasa de numeros decimales a romanos (El framwork está en el espacio de nombres "TestSuite").
#include <iostream> #include <string> #include "Test.h" std::string ARomano(int Decimal ) { static char *unidades[] = // Unidades de 0 a 9 { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }; static char *decenas[] = // Decenas de 0 a 90 { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }; static char *centenas[] = // Centenas de 0 a 900 { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }; static char *millares[] = // Millares de 0 a 3000 { "", "M", "MM", "MMM" }; return std::string(millares[ (Decimal / 1000) ]) + std::string(centenas[ (Decimal / 100) % 10 ]) + std::string(decenas[ (Decimal / 10) % 10 ]) + std::string(unidades[ (Decimal) % 10 ]); } // Para probar la función de arriba creamos una clase, derivada de Test, que pruebe algunos valores. class TestARomano : public TestSuite::Test { public: void run() { test_(ARomano(1)=="I"); test_(ARomano(5)=="V"); test_(ARomano(13)=="XIII"); test_(ARomano(99)=="XCIX"); test_(ARomano(444)=="CDXLIV"); test_(ARomano(720)=="DCCXX"); test_(ARomano(2803)=="MMDCCCIII"); test_(ARomano(3888)=="MMMDCCCLXXXVIII"); test_(ARomano(3999)=="MMMCMXCIX"); } }; int main() { int n; TestARomano mitest; // Instancio la clase. mitest.run(); // Pruebo si hubieron errors. assert(mitest.report()==0); // Muestro los resultados. No tienen que haber habido errores. std::cout<<std::endl<<"Ingrese numeros en decimal entre 1 y 3999 para transformarlos en romanos. Ingrese <1 o >3999 para salir."<<std::endl; do { std::cout<<"Decimal: "; std::cin>>n; if((n<=0)||(n>3999)) break; std::cout<<"Romano: "<<ARomano(n)<<std::endl; }while(1); return 0; }
La salida del programa es la siguiente:
Test "class TestARomano":
Passed: 9 Failed: 0
Ingrese numeros en decimal entre 1 y 3999 para transformarlos en romanos. Ingres
e <1 o >3999 para salir.
Decimal: 453
Romano: CDLIII
Decimal: 43
Romano: XLIII
Decimal: 0
Un ejemplo donde probamos más de una función se muestra a continuación. Para eso utilizamos el framework, para integrar los diferentes tests.
#include <iostream> #include <string> #include "Test.h" #include "Suite.h" // Funciones "tontas" para mostrar el uso del framework. int suma(int a,int b){return a+b;} int resta(int a,int b){return a-b;} int multiplicacion(int a,int b){return a*b;} int division(int a,int b){return a/b;} // Probamos. class TestSuma : public TestSuite::Test { public: void run() { test_(suma(1,1)==2); test_(suma(2,1)==3); test_(suma(1,2)==3); test_(suma(2,5)==7); test_(suma(100,29)==129); test_(suma(5,1)>5); test_(suma(1,3)<5); } }; class TestResta : public TestSuite::Test { public: void run() { test_(resta(1,1)==0); test_(resta(2,1)==1); test_(resta(1,2)==-1); test_(resta(2,5)==-3); test_(resta(100,29)==71); test_(resta(5,1)>3); test_(resta(1,3)<1); } }; class TestMultiplicacion : public TestSuite::Test { public: void run() { test_(multiplicacion(1,1)==1); test_(multiplicacion(2,1)==2); test_(multiplicacion(1,2)==2); test_(multiplicacion(2,5)==10); test_(multiplicacion(100,29)==2900); test_(multiplicacion(5,1)>4); test_(multiplicacion(1,3)<4); } }; class TestDivision : public TestSuite::Test { public: void run() { test_(multiplicacion(1,1)==1); test_(multiplicacion(2,1)==2); test_(multiplicacion(1,2)==0); test_(multiplicacion(2,5)==0); test_(multiplicacion(100,29)==3); test_(multiplicacion(5,1)>4); test_(multiplicacion(1,3)<1); } }; int main() { TestSuite::Suite basicas("Operaciones basicas"); basicas.addTest(new TestSuma); basicas.addTest(new TestResta); basicas.addTest(new TestMultiplicacion); basicas.addTest(new TestDivision); basicas.run(); std::cout<<std::endl<<"Fallos: "<<basicas.report(); basicas.free(); // Le digo que los punteros estaban en el heap y que los borre. return 0; }
La salida del ejemplo anterior es:
class TestDivisionfailure: (multiplicacion(1,2)==0) , c:\documents and settings\
daniel\mis documentos\visual studio 2008\projects\romano\romano\romanos.cpp (lin
e 65)
class TestDivisionfailure: (multiplicacion(2,5)==0) , c:\documents and settings\
daniel\mis documentos\visual studio 2008\projects\romano\romano\romanos.cpp (lin
e 66)
class TestDivisionfailure: (multiplicacion(100,29)==3) , c:\documents and settin
gs\daniel\mis documentos\visual studio 2008\projects\romano\romano\romanos.cpp (
line 67)
class TestDivisionfailure: (multiplicacion(1,3)<1) , c:\documents and settings\d
aniel\mis documentos\visual studio 2008\projects\romano\romano\romanos.cpp (line
69)
Suite "Operaciones basicas"
===========================
Test "class TestSuma":
Passed: 7 Failed: 0
Test "class TestResta":
Passed: 7 Failed: 0
Test "class TestMultiplicacion":
Passed: 7 Failed: 0
Test "class TestDivision":
Passed: 3 Failed: 4
===========================
Fallos: 4
Claramente se puede ver cuales fueron los fallos. Me olvidé de cambiar "multiplicación" por " división en el test de división (porque copié y pegué el código.
El siguiente código ya funciona bien:
#include <iostream> #include <string> #include "Test.h" #include "Suite.h" // Funciones "tontas" para mostrar el uso del framework. int suma(int a,int b){return a+b;} int resta(int a,int b){return a-b;} int multiplicacion(int a,int b){return a*b;} int division(int a,int b){return a/b;} // Probamos. class TestSuma : public TestSuite::Test { public: void run() { test_(suma(1,1)==2); test_(suma(2,1)==3); test_(suma(1,2)==3); test_(suma(2,5)==7); test_(suma(100,29)==129); test_(suma(5,1)>5); test_(suma(1,3)<5); } }; class TestResta : public TestSuite::Test { public: void run() { test_(resta(1,1)==0); test_(resta(2,1)==1); test_(resta(1,2)==-1); test_(resta(2,5)==-3); test_(resta(100,29)==71); test_(resta(5,1)>3); test_(resta(1,3)<1); } }; class TestMultiplicacion : public TestSuite::Test { public: void run() { test_(multiplicacion(1,1)==1); test_(multiplicacion(2,1)==2); test_(multiplicacion(1,2)==2); test_(multiplicacion(2,5)==10); test_(multiplicacion(100,29)==2900); test_(multiplicacion(5,1)>4); test_(multiplicacion(1,3)<4); } }; class TestDivision : public TestSuite::Test { public: void run() { test_(division(1,1)==1); test_(division(2,1)==2); test_(division(1,2)==0); test_(division(2,5)==0); test_(division(100,29)==3); test_(division(5,1)>4); test_(division(1,3)<1); } }; int main() { TestSuite::Suite basicas("Operaciones basicas"); basicas.addTest(new TestSuma); basicas.addTest(new TestResta); basicas.addTest(new TestMultiplicacion); basicas.addTest(new TestDivision); basicas.run(); std::cout<<std::endl<<"Fallos: "<<basicas.report(); basicas.free(); // Le digo que los punteros estaban en el heap y que los borre. return 0; }
La salida es:
Suite "Operaciones basicas"
===========================
Test "class TestSuma":
Passed: 7 Failed: 0
Test "class TestResta":
Passed: 7 Failed: 0
Test "class TestMultiplicacion":
Passed: 7 Failed: 0
Test "class TestDivision":
Passed: 7 Failed: 0
===========================
Fallos: 0
En el libro online hay un ejemplo más completo[2]. Pero esto ya nos sirve para empezar a trabajar con pruebas automáticas.
A continuación el código del framework:
Test.h:
//: TestSuite:Test.h // From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison. // (c) 1995-2004 MindView, Inc. All Rights Reserved. // See source code use permissions stated in the file 'License.txt', // distributed with the code package available at www.MindView.net. #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 ///:~
Test.cpp:
//: TestSuite:Test.cpp {O} // From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison. // (c) 1995-2004 MindView, Inc. All Rights Reserved. // See source code use permissions stated in the file 'License.txt', // distributed with the code package available at www.MindView.net. #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; } ///:~
Suite.h:
//: TestSuite:Suite.h // From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison. // (c) 1995-2004 MindView, Inc. All Rights Reserved. // See source code use permissions stated in the file 'License.txt', // distributed with the code package available at www.MindView.net. #ifndef SUITE_H #define SUITE_H #include <vector> #include <stdexcept> #include "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 ///:~
Suite.cpp:
//: TestSuite:Suite.cpp {O} // From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison. // (c) 1995-2004 MindView, Inc. All Rights Reserved. // See source code use permissions stated in the file 'License.txt', // distributed with the code package available at www.MindView.net. #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(); } } ///:~
FOOTNOTES
1. The benefits of programming with assertions.↑
2. Para más detalles consultar el título "A simple unit test framework", en el libro online. (en inglés)↑
2. post está basado en el libro "Thinking in C++ - Volume Two" de Bruce Eckel y Chuck Allison.
A medida que un programa va creciendo en tamaño y complejidad, se hace cada vez más difícil rastrear los errores a través del código. Peor todavía con los errores que no saltan a simple vista.
Una de las ventajas de la programación modular es que, al dividir un problema en muchos problemas mas pequeños, si cada parte funciona bien, todo funciona bien. Y es más fácil hacer que una pequeña parte funcione bien, cada vez que se diseña una, que diseñar todo un programa enorme y tratar de que funcione bien.
Entonces vamos a ver algunas técnicas para ir asegurándonos que cada parte de nuestro programa funcione bien a medida que lo vamos haciendo. Así, cuando unamos todas las partes, el programa entero va a funcionar bien.
Aserciones.
Un algoritmo es, entre otras cosas, una expresión de nuestro intento por resolver un problema. Éste debería dejar en claro al lector (inclusive el escritor mismo) exactamente qué se estaba pensando cuando se diseñó esa porción de código así. En ciertos puntos del programa, se deberían poder hacer ciertas aserciones o aseveraciones.
Algunas ventajas[1]:
- Hace que, lo que de otra forma hubieras puesto como comentario, sea verificado en tiempo de ejecución para ver si realmente es así.
- Te permite expresar en el código lo que vos considerás como verdadero en un cierto punto de ejecución.
- Si no se cumple inmediatamente termina el programa.
- Probablemente ya hayas escrito comentarios en tu código. ¿Porqué no convertirlo en aserciones y hacer tu código más robusto?
- Podés estar seguro de que, si el código llegó ahí, todo lo que habías asumido como verdadero hasta ese punto se cumplió.
- No hacen, en absoluto, el código más lento (una vez compilado en release). Sólo existen para compilaciones "debug". Para "release" desaparecen.
Un programa sin aserciones se vería así:
#include <iostream> #include <algorithm> int main() { int v[]={0,1,2,3,4,5,6,7,8,9}; int tam=sizeof v / sizeof *v; int min=*std::min_element(v,v+tam); int max=*std::max_element(v,v+tam); std::cout<<"Min: "<<min<<std::endl; std::cout<<"Max: "<<max; return 0; }
Uno con aserciones sería:
#include <iostream> #include <algorithm> #include <cassert> int main() { int v[]={0,1,2,3,4,5,6,7,8,9}; int tam=sizeof v / sizeof *v; assert(tam==10); int min=*std::min_element(v,v+tam); assert(min==0); int max=*std::max_element(v,v+tam); assert(max==9); std::cout<<"Min: "<<min<<std::endl; std::cout<<"Max: "<<max; return 0; }
Cuando escribí este ejemplo, me confundí en varias cosas. Por ejemplo, al principio supuse "sizeof v" como 10, cuando en realidad era 40 ya que cada int me ocupaba 4 bytes. Saltó el assert.
También puse "assert(max==10)". Otra vez saltó, porque distraído no me dí cuenta que aunque el tamaño era 10, el mayor elemento era 9.
Como verás, es muy útil para ayudar al programador. Llená tu código de aserciones. Por ejemplo antes de evaluar la raiz cuadrada de un número, verificá que sea >=0. O antes de dividir por x verificá que "assert(x!=0). Te va a ahorrar dolores de cabeza.
La sintaxis de assert es:
void assert (int expression);
Está definida en la cabecera "<cassert>".
Para la versión final de tu programa, luego de haber probado que anda todo bien, eliminá automáticamente todas aserciones definiendo, antes de la inclusión de <cassert>, lo siguiente:
#define NDEBUG
La definición de assert, en <cassert> es equivalente a esta:
#ifdef NDEBUG #define assert(cond) ((void)0) #else void assertImpl(const char*, const char*,long); #define assert(cond)\ ((cond)?(void)0:assertImpl(???)) #endif
Estructura Automatizada de pruebas (Automated Test Framework).
Muchas veces los programadores, luego de haber construido una función, la probamos llamándola con algunos valores y fijándonos que nos retorna.
Esto es tedioso y muy propenso a errores. Las máquinas lo hacen mejor que los humanos. Entonces ¿porqué no automatizar el proceso?
El libro Thinking In C++ - Volume 2: Practical Programming presenta "la unidad de test automatizado más simple que pueda funcionar".
El código lo podés descargar de acá o copiarlo del final del post.
Uso[2]:
A continuación pongo como un ejemplo un programa que pasa de numeros decimales a romanos (El framwork está en el espacio de nombres "TestSuite").
#include <iostream> #include <string> #include "Test.h" std::string ARomano(int Decimal ) { static char *unidades[] = // Unidades de 0 a 9 { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }; static char *decenas[] = // Decenas de 0 a 90 { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }; static char *centenas[] = // Centenas de 0 a 900 { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }; static char *millares[] = // Millares de 0 a 3000 { "", "M", "MM", "MMM" }; return std::string(millares[ (Decimal / 1000) ]) + std::string(centenas[ (Decimal / 100) % 10 ]) + std::string(decenas[ (Decimal / 10) % 10 ]) + std::string(unidades[ (Decimal) % 10 ]); } // Para probar la función de arriba creamos una clase, derivada de Test, que pruebe algunos valores. class TestARomano : public TestSuite::Test { public: void run() { test_(ARomano(1)=="I"); test_(ARomano(5)=="V"); test_(ARomano(13)=="XIII"); test_(ARomano(99)=="XCIX"); test_(ARomano(444)=="CDXLIV"); test_(ARomano(720)=="DCCXX"); test_(ARomano(2803)=="MMDCCCIII"); test_(ARomano(3888)=="MMMDCCCLXXXVIII"); test_(ARomano(3999)=="MMMCMXCIX"); } }; int main() { int n; TestARomano mitest; // Instancio la clase. mitest.run(); // Pruebo si hubieron errors. assert(mitest.report()==0); // Muestro los resultados. No tienen que haber habido errores. std::cout<<std::endl<<"Ingrese numeros en decimal entre 1 y 3999 para transformarlos en romanos. Ingrese <1 o >3999 para salir."<<std::endl; do { std::cout<<"Decimal: "; std::cin>>n; if((n<=0)||(n>3999)) break; std::cout<<"Romano: "<<ARomano(n)<<std::endl; }while(1); return 0; }
La salida del programa es la siguiente:
Test "class TestARomano":
Passed: 9 Failed: 0
Ingrese numeros en decimal entre 1 y 3999 para transformarlos en romanos. Ingres
e <1 o >3999 para salir.
Decimal: 453
Romano: CDLIII
Decimal: 43
Romano: XLIII
Decimal: 0
Un ejemplo donde probamos más de una función se muestra a continuación. Para eso utilizamos el framework, para integrar los diferentes tests.
#include <iostream> #include <string> #include "Test.h" #include "Suite.h" // Funciones "tontas" para mostrar el uso del framework. int suma(int a,int b){return a+b;} int resta(int a,int b){return a-b;} int multiplicacion(int a,int b){return a*b;} int division(int a,int b){return a/b;} // Probamos. class TestSuma : public TestSuite::Test { public: void run() { test_(suma(1,1)==2); test_(suma(2,1)==3); test_(suma(1,2)==3); test_(suma(2,5)==7); test_(suma(100,29)==129); test_(suma(5,1)>5); test_(suma(1,3)<5); } }; class TestResta : public TestSuite::Test { public: void run() { test_(resta(1,1)==0); test_(resta(2,1)==1); test_(resta(1,2)==-1); test_(resta(2,5)==-3); test_(resta(100,29)==71); test_(resta(5,1)>3); test_(resta(1,3)<1); } }; class TestMultiplicacion : public TestSuite::Test { public: void run() { test_(multiplicacion(1,1)==1); test_(multiplicacion(2,1)==2); test_(multiplicacion(1,2)==2); test_(multiplicacion(2,5)==10); test_(multiplicacion(100,29)==2900); test_(multiplicacion(5,1)>4); test_(multiplicacion(1,3)<4); } }; class TestDivision : public TestSuite::Test { public: void run() { test_(multiplicacion(1,1)==1); test_(multiplicacion(2,1)==2); test_(multiplicacion(1,2)==0); test_(multiplicacion(2,5)==0); test_(multiplicacion(100,29)==3); test_(multiplicacion(5,1)>4); test_(multiplicacion(1,3)<1); } }; int main() { TestSuite::Suite basicas("Operaciones basicas"); basicas.addTest(new TestSuma); basicas.addTest(new TestResta); basicas.addTest(new TestMultiplicacion); basicas.addTest(new TestDivision); basicas.run(); std::cout<<std::endl<<"Fallos: "<<basicas.report(); basicas.free(); // Le digo que los punteros estaban en el heap y que los borre. return 0; }
La salida del ejemplo anterior es:
class TestDivisionfailure: (multiplicacion(1,2)==0) , c:\documents and settings\
daniel\mis documentos\visual studio 2008\projects\romano\romano\romanos.cpp (lin
e 65)
class TestDivisionfailure: (multiplicacion(2,5)==0) , c:\documents and settings\
daniel\mis documentos\visual studio 2008\projects\romano\romano\romanos.cpp (lin
e 66)
class TestDivisionfailure: (multiplicacion(100,29)==3) , c:\documents and settin
gs\daniel\mis documentos\visual studio 2008\projects\romano\romano\romanos.cpp (
line 67)
class TestDivisionfailure: (multiplicacion(1,3)<1) , c:\documents and settings\d
aniel\mis documentos\visual studio 2008\projects\romano\romano\romanos.cpp (line
69)
Suite "Operaciones basicas"
===========================
Test "class TestSuma":
Passed: 7 Failed: 0
Test "class TestResta":
Passed: 7 Failed: 0
Test "class TestMultiplicacion":
Passed: 7 Failed: 0
Test "class TestDivision":
Passed: 3 Failed: 4
===========================
Fallos: 4
Claramente se puede ver cuales fueron los fallos. Me olvidé de cambiar "multiplicación" por " división en el test de división (porque copié y pegué el código.
El siguiente código ya funciona bien:
#include <iostream> #include <string> #include "Test.h" #include "Suite.h" // Funciones "tontas" para mostrar el uso del framework. int suma(int a,int b){return a+b;} int resta(int a,int b){return a-b;} int multiplicacion(int a,int b){return a*b;} int division(int a,int b){return a/b;} // Probamos. class TestSuma : public TestSuite::Test { public: void run() { test_(suma(1,1)==2); test_(suma(2,1)==3); test_(suma(1,2)==3); test_(suma(2,5)==7); test_(suma(100,29)==129); test_(suma(5,1)>5); test_(suma(1,3)<5); } }; class TestResta : public TestSuite::Test { public: void run() { test_(resta(1,1)==0); test_(resta(2,1)==1); test_(resta(1,2)==-1); test_(resta(2,5)==-3); test_(resta(100,29)==71); test_(resta(5,1)>3); test_(resta(1,3)<1); } }; class TestMultiplicacion : public TestSuite::Test { public: void run() { test_(multiplicacion(1,1)==1); test_(multiplicacion(2,1)==2); test_(multiplicacion(1,2)==2); test_(multiplicacion(2,5)==10); test_(multiplicacion(100,29)==2900); test_(multiplicacion(5,1)>4); test_(multiplicacion(1,3)<4); } }; class TestDivision : public TestSuite::Test { public: void run() { test_(division(1,1)==1); test_(division(2,1)==2); test_(division(1,2)==0); test_(division(2,5)==0); test_(division(100,29)==3); test_(division(5,1)>4); test_(division(1,3)<1); } }; int main() { TestSuite::Suite basicas("Operaciones basicas"); basicas.addTest(new TestSuma); basicas.addTest(new TestResta); basicas.addTest(new TestMultiplicacion); basicas.addTest(new TestDivision); basicas.run(); std::cout<<std::endl<<"Fallos: "<<basicas.report(); basicas.free(); // Le digo que los punteros estaban en el heap y que los borre. return 0; }
La salida es:
Suite "Operaciones basicas"
===========================
Test "class TestSuma":
Passed: 7 Failed: 0
Test "class TestResta":
Passed: 7 Failed: 0
Test "class TestMultiplicacion":
Passed: 7 Failed: 0
Test "class TestDivision":
Passed: 7 Failed: 0
===========================
Fallos: 0
En el libro online hay un ejemplo más completo[2]. Pero esto ya nos sirve para empezar a trabajar con pruebas automáticas.
A continuación el código del framework:
Test.h:
//: TestSuite:Test.h // From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison. // (c) 1995-2004 MindView, Inc. All Rights Reserved. // See source code use permissions stated in the file 'License.txt', // distributed with the code package available at www.MindView.net. #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 ///:~
Test.cpp:
//: TestSuite:Test.cpp {O} // From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison. // (c) 1995-2004 MindView, Inc. All Rights Reserved. // See source code use permissions stated in the file 'License.txt', // distributed with the code package available at www.MindView.net. #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; } ///:~
Suite.h:
//: TestSuite:Suite.h // From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison. // (c) 1995-2004 MindView, Inc. All Rights Reserved. // See source code use permissions stated in the file 'License.txt', // distributed with the code package available at www.MindView.net. #ifndef SUITE_H #define SUITE_H #include <vector> #include <stdexcept> #include "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 ///:~
Suite.cpp:
//: TestSuite:Suite.cpp {O}
// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
// See source code use permissions stated in the file 'License.txt',
// distributed with the code package available at www.MindView.net.
#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();
}
} ///:~<↑
Aún no hay trackbacks.
25 junio, 2009 - 14:00
hola que tal a mi me gustaria que publiques mas informacion personal sobre vos, como fotos y datos privados, si tenes novia, si sos virgen…etc.
25 junio, 2009 - 14:53
jaja