|
BUnit.h
: Un módulo C++ simple para aprender prueba unitaria de programas
Adolfo Di Mare |
Se promociona el uso de herramientas para la prueba unitaria de programas. Se propone un esquema de implementación de programas en el que tanto la especificación y la prueba de cada pieza de software se desarrolla antes de la codificación de los algoritmos. Se reporta la experiencia del uso de este enfoque de construcción de programas en estudiantes de programación. | It is pleaded for the use of unit testing program tools. A scheme for program implementation in which both the specification and the unit test for each piece of software is developed before coding algorithms. The experience on using this approach with programming students is reported. |
BUnit-SIIE-2008.pdf
---
BUnit-SIIE-2008.ppt
Mejorar la productividad del programador es esencial para reducir el costo de construir sistemas de computación, pues la mayor parte del presupuesto se invierte pagándoles. Por eso, el entrenamiento de programadores enfatiza lograr que utilicen herramientas sofisticadas que les ayuden a aumentar su productividad. Aunque ya se han logrando mejoras en la productividad usando formas innovadoras de organización, como las metodologías "ágiles" de "programación extrema" (XP: eXtreme Programming) [Beck-1999] o el uso de los lenguajes de cuarta generación [Martin-1986], todavía existe la expectativa de reducir más el costo de la programación. Aquí no se presenta la "pomada canaria"[1] que permitirá reducir significativamente el costo de la automatización, pero sí se presenta una contribución que permitirá ponerle una "curita"[2] a la herida por donde se sangra ese dinero que algún día permitirá ahorrar la automatización completa de la construcción de programas y sistemas. Paulatinamente se logrará llegar a la meta final para alcanzar el costo mínimo en la construcción de sistemas.
En este artículo se describe la herramienta
BUnit
,
un módulo
[B]ásico para prueba [unit]aria de programas,
que sirve para lograr que un programador C++ adquiera la buena
costumbre de diseñar, especificar y probar antes de
codificar los algoritmos de un programa. La cualidad más
importante del componente BUnit
es su sencillez, que
lo hace ideal para mejorar la construcción de programas;
está diseñado para que los alumnos aprendan a hacer
prueba unitaria de programas y es un paso hacia el uso de las
otras herramientas más sofisticadas que usará el
programador en su práctica profesional. Aquí se
argumenta que siempre es necesario:
Como explico en mi artículo "Uso de Doxygen para especificar módulos y programas" [DiM-2007b], Doxygen es una herramienta que permite integrar en la codificación de los algoritmos su especificación, de manera que el trabajo que el programador invierte en la especificación contribuye directamente a la implementación final. Las principales cualidades de Doxygen son su flexibilidad y facilidad de uso.
p2-ta-1.dxg
-
Archivo de configuración Doxygen
En la Figura 1 está el archivo de configuración básico que cualquier estudiante puede usar para generar su documentación Doxygen rápidamente. Los programadores muchas veces omiten las especificaciones de las piezas de programación que crean, pero con una herramienta como Doxygen es fácil constatar que falta documentación, pues basta generarla y verificar si algún módulo carece de ella. Es así como se puede inculcar en los programadores la disciplina de especificar antes de programar, o sea, de diseñar antes de construir, lo que mejora la calidad del producto final. Especificar no es una labor que los programadores ejecuten con facilidad. A pesar de que Doxygen facilita inculcar en el programador la costumbre de definir el "qué" antes del "cómo", la práctica muestra que los programadores generalmente evitan redactar especificaciones porque prefieren codificar inmediatamente para que otra persona haga el resto: del dicho al hecho hay mucho trecho.
Así como las bailarinas son felices cuando danzan, los programadores obtienen su gratificación cuando programan. Escriben especificaciones obligados, pero prefieren invertir su tiempo codificando (muchas veces están anuentes a escribir una corta descripción de lo que hace cada módulo, aunque no quede redactada de la mejor manera). Debido a que es necesario que el programa funcione para que el cliente pague la siguiente cuota pactada, el énfasis del entrenamiento del programador está en lograr que implemente pronto el programa. Por eso pierden importancia la documentación y, en especial, la especificación de cada uno de los módulos de un sistema. Es particularmente difícil que los programadores aprendan a especificar sus módulos, pues pocos tienen la paciencia de redactar con cuidado sus ideas: son "ingenieros", no "poetas". Herramientas como Doxygen sirven para mejorar significativamente la situación actual. En la Universidad de Costa Rica se usa este generador de documentación, desarrollado por Dimitri van Heesch [VH-2005], porque es una herramienta que cubre un espectro más amplio que el de herramientas como "JavaDoc", pues sirve generar la documentación para los lenguajes más populares: C++, Java, C#, Visual Basic, SQL, etc.
En mi universidad algunos profesores sostienen que: "...los estudiantes están acostumbrados a no diseñar, se sientan de una vez a codificar sin pensar antes en nada. Si les pedimos que presenten su diseño de antemano, les da una pereza enorme porque muy poco de esta parte se puede hacer "automatizada". Ni siquiera se les ocurre que es necesario probar los programas. Casi siempre mis asistentes me dicen que el sistema "se cae", pues los estudiantes simplifican hasta el ridículo las pruebas para creer que todo lo hicieron bien".
La afinidad natural del programador por la codificación inspiró a Kent Beck a proponer la programación extrema [Beck-1999], en donde defiende el diseñar, probar y programar cada módulo, anteponiendo la prueba a la codificación. En efecto, en el noveno capítulo de su libro afirma que "las cualidades de los programas que no pueden ser demostradas por pruebas automáticas simplemente no existen". Por eso aquí se explica cómo mejorar la construcción de sistemas utilizando la prueba de programas como una técnica para afinar la especificación, lo que efectivamente promueve la estrategia de construcción de sistemas que predican los obispos de la programación extrema: diseñe, luego especifique y pruebe y, por último, codifique.
A veces parece que usar programación extrema libera al constructor de programas de la responsabilidad de redactar especificaciones, sustituyéndolas por módulos y datos de prueba. Lo mejor es facilitarle el trabajo al programador para que pueda escribir la especificación junto con los datos, como se propone aquí.
El uso de herramientas para probar programas no representa un enfoque revolucionario, pues no propone un nuevo paradigma de programación, y más bien complementa los esquemas formales usados para construir programas, en lugar de modificarlos o anularlos. Por eso se puede usar la prueba de programas como una parte adicional de cualquier pieza de software.
Ya no es nuevo argumentar que las especificaciones de cada módulo deben estar definidas antes de codificar los algoritmos [DiM-2007a]. Desafortunadamente, redactar las especificaciones de cada módulo es un trabajo complicado aunque también es frecuente encontrar que el código que no ha sido probado suficientemente. Los creadores de la programación extrema han propuesto una solución a este problema, que consiste en cambiar el paradigma de "diseñar y luego programar" por un proceso en el que se destaca la prueba de programas. Aquí se propone mejorar este estilo de construcción de sistemas incorporando la prueba del módulo como parte de su especificación.
Las innovaciones son difíciles de asimilar. Recuerdo la efervescencia que suscitó el profesor Claudio Gutiérrez cuando decidió enseñar programación funcional usando el lenguaje Scheme como primer lenguaje de programación, o la batalla para lograr introducir la Programación Orientada a Objetos [OOP]. Los ticos usamos mucho el adagio que dice que "Al chancho como lo crían" porque sabemos que es más efectivo enseñar bien desde el principio, cuando los muchachos todavía no han adquirido malas costumbres, para moldearles sus costumbres cuando todavía no han sido influenciados por las malas prácticas de trabajo.
En un ambiente académico es difícil lograr que el estudiante dedique suficiente tiempo a "probar programas" pues si el estudiante invierte mucho tiempo en esta actividad no podrá concluir el proyecto; en la universidad, la "potencia" del programador se mide por el "tamaño" del programa que ha implementado. Quienquiera que califique el trabajo del estudiante tampoco tiene gran interés en constatar que cada módulo fue probado suficientemente, pues para asignar una calificación lo más práctico es verificar el caso más general. Esto ocurre también en los ambientes de trabajo, en donde se enfatiza el "terminar esta etapa pronto" en lugar de "verificar exhaustivamente que todo funciona". Por eso, naturalmente, probar programas es percibido como un costo adicional que nadie quiere pagar.
Una barrera que es necesario franquear es lograr entrenar a los profesores para que acepten y adopten la modificación del enfoque de enseñanza que implica construir programas haciendo las especificaciones y los datos de prueba antes que los algoritmos, pues a veces los profesores son reacios a cambiar sus esquemas de trabajo. Puede ocurrir también que algunos docentes sientan temor al enseñar un tópico novedoso como lo es la prueba unitaria de programas, temor que se puede ver reducido mucho si la herramienta que usan es suficientemente adecuada y sencilla.
Si los estudiantes usan una herramienta, poco a poco sus profesores también aprenderán a usarla. Esta es la forma en que el uso de "Doxygen" fue introducido en la Universidad de Costa Rica, en donde los profesores se han acostumbrado a que sus alumnos especifiquen con propiedad sus programas, para lo que Doxygen es una herramienta ideal.
Los profesores necesitan contar con una herramienta que les
permita aprender rápido y también enseñar
rápido. Hay varias herramientas en uso, como por ejemplo
CppUnit
[FLetc-2007], pero tienen una importante
deficiencia: como son relativamente complicadas, es difícil
usarlas para adiestrar personas que apenas están
aprendiendo a programar. La innovación de este enfoque es
que sirve para acostumbrar a los estudiantes a programar
escribiendo primero la especificación y también los
datos de prueba, de manera que las nuevas generaciones de
programadores lo hagan de forma natural y no lo tomen como una
imposición o una incomodidad.
En un ambiente de trabajo de empresa en que la prueba unitaria de
programas no es una práctica cotidiana es posible utilizar
rápidamente Doxygen, para luego incorporar el uso de
pruebas unitarias usando el
módulo
BUnit
aquí descrito.
Es un hecho que no pasaron 3 décadas desde la invención de la computación cuando se descubrió que por lo menos la mitad de los recursos se invierten en mejorar o arreglar programas [Boehm-1981]. El mantenimiento de sistemas depende mucho de que los módulos de programas estén debidamente documentados, pues de lo contrario cada vez que hay que hacer un pequeño cambio es necesario que el programador reconstruya completo el módulo. Aunque es posible lograr que un grupo de programadores con suficiente tiempo y entrenamiento logren mantener en su mente los detalles y vericuetos de cada uno de los módulos de un sistema, la falta de documentación siempre resulta en un incremento sustancial de los costos. Por eso, vale la pena documentar cuando se crea le programa, y también vale la pena automatizar el proceso de prueba de módulos, para evitar que un pequeño cambio producto de una mejora resulte en un cataclismo contra el sistema.
Cualquier programador sabe que debe probar su programa porque lo natural es que la primera codificación esté incorrecta. Para realizar estas pruebas preliminares muchos optan por escribir "pedaciticos"[3] de código, para probar parte por parte la funcionalidad que paulatinamente el programador el agrega a su código. Desafortunadamente, todo ese trabajo se pierde, porque no queda grabado en un repositorio digital que permita reutilizarlo de nuevo. Cualquier programador puede ser convencido fácilmente de que a nadie ayuda descartar ese trabajo, pero ese desperdicio ocurre constantemente porque mucha gente todavía no sabe cómo evitarlo. "Probar y luego codificar" es la estrategia de programación extrema [Beck-1999].
Algunos autores han afirmado que probar programas es una actividad "intelectualmente desafiante" [Myers-2004] o "adorable" [BG-1998]. Estas afirmaciones buscan convencer a los programadores para que usen las técnicas de prueba unitaria de programas, pero no convencen porque el programador prefiere escribir código hoy y también escribir código mañana a pesar de que al darle un lugar más preponderante a la programación usando datos de prueba se logra un diseño de software que es más reutilizable porque también se disminuye el acoplamiento entre módulos [BG-1998].
Es más fácil convencer a un programador de que escriba sus módulos de prueba si se le presenta la situación de una manera diferente. Un caso de prueba unitaria siempre es, a fin de cuentas, un programa. Si se usan herramientas de prueba, el programador pueden concentrarse en la escritura del caso de prueba porque la herramienta se lo facilita. Debido a que siempre es necesario probar cualquier programa, es posible argumentarle al programador que a fin de cuentas ahorrará tiempo si programa su caso de prueba una vez, en lugar de hacer las pruebas informalmente sin dejarlas bien escritas e implementadas. En otras palabras, es posible argumentarle a un programador que se muestra reacio a incorporar módulos de prueba que, a fin de cuentas, si lo hace termina el trabajo de programación más rápido, y con una mejor calidad. En otras palabras, la prueba unitaria de módulos mejora la productividad del programador.
Para aquellos programadores que prefieren construir sus programas usando desarrollo de arriba hacia abajo (Top-Down development), el proceso ideal de programación debiera de consistir de 3 tres etapas: prueba → especificación → algortimo. Otros programadores, que prefieren construir sus programas de abajo hacia arriba (Bottom-Up development), ejecutaría sus 3 etapas invirtiendo el orden de las 2 primeras: especificación → prueba → algortimo. Es natural que la codificación del algoritomo sea la última etapa del proceso, pues antes de "llegar" es necesario definir cuál es el "destino": quien no sabe adónde vá, ¡llega a otro lado!
Los seguidores de la programación extrema proponen la programación como una secuencia de etapas diferentes: agregue una prueba, hágala fallar, codifique el algoritmo para pasar la prueba y por último elimine la redundancia ("add a test, get it to fail, write code to pass the test and remove duplication") [Beck-2002]. Este enfoque no toma en cuenta que es importante definir la especificación para cualquier módulo y tampoco sirve para aquellos trabajos de programación en los que la complejidad de cada módulo es muy grande. Por ejemplo, escribir un módulo para encontrar la derivada o integral de una función de varias variables no es una tarea que pueda ser ejecutada usando una estrategia simple de prueba y error que no tomaría en cuenta muchos casos especiales que es necesario considerar. Sin embargo, para programas simples, como lo son los que generalmente forman parte de una aplicación de consulta y actualización de una base de datos relacional, el enfoque de prueba y error puede resultar suficiente.
Un buen consejo que puede seguir el programador que se siente
reacio a adoptar la disciplina de escribir el código
para probar programas es "escribir un módulo de prueba" en
cada ocasión en que se vea tentado a usar una
instrucción "print
" de impresión
[Fowler-1999].
BUnit
Chuck Allison implementó un pequeño módulo
para probar programas C++
[Allison-2000] que disminuye el costo
de aprender a usar paquetes de calidad profesional como
CppUnit
. Sin embargo, usar herramientas similares a
CppUnit
requiere del programador un entendimiento
profundo de cómo están construidas, y no sirve para
mejorar la especificación de los módulos. El
módulo C++
BUnit
que se presenta aquí es más simple que el propuesto
por Allison, y tiene la ventaja de que se puede usar con facilidad
para mejorar la documentación.
+----------------+ | TestCase | +----------------+ | run() = 0 | | successCount() | | failureCount() | | assertTrue() | +----------------+ | Fixture | | - setUp() | | - tearDown() | +----------------+ /\ || +-------------------------+ | TestSuite< TestCase > | +-------------------------+ | addTest ( TestCase & ) | | addSuite( TestSuite & ) | +-------------------------+ |
BUnit
Como se muestra en la
Figura 2, BUnit
está
constituida por 2 clases. La clase base TestCase
que
contiene la implementación de la prueba y también la
colección en donde están almacenados los resultados
de ejecutar la prueba. La clase
emplantillada TestSuite<>
contiene varias
pruebas y, por comodidad, está derivada de
TestCase
(se han usado los nombres en inglés
para mantener la compatibilidad con otras herramientas de la
familia "xUnit
").
La cualidad que distingue a BUnit
es que es muy
simple, tanto que su implementación completa reside en un
único archivo de
encabezado: BUnit
es una de las más
simples herramientas de la familia "xUnit
", que es el
nombre acuñado a partir de la herramienta
Java originaria:
JUnit
(varios de
los miembros de esta familia de herramientas para prueba unitaria
de programas están descritas en
[Llopis-2004]). Si la herramienta es
simple el programador opondrá menos objeciones para usarla
y también servirá para integrar al aprendizaje de
manera natural la programación el uso de la prueba unitaria
de programas. Como BUnit
es simple también es
más fácil de documentar. Un diseño simple
contiene sólo las cualidades más importantes de la
herramienta de manera que sea el uso de otras herramientas
más complicadas en donde se incorporen las opciones y
facilidades más sofisticadas.
Herramientas más complejas incorporan elementos
gráficos que ayudan al programador, como por ejemplo el uso
de una "barra de progreso" que indique cuánto falta para
terminar, lo que requiere mucha maquinaria programática que
es necesario tomar en cuenta y que por lo tanto incrementa la
complejidad de la herramienta. Otra importante cualidad de
BUnit
es que se usa de una manera similar a
JUnit
, la herramienta de prueba unitaria que la mayor
parte de los programadores Java conocen, lo que facilita el
entrenamiento de nuevos programadores en herramientas más
sofisticadas, quienes eventualmente deben hacer esa
transición. BUnit
se parece a
JUnit
para facilitar la transición entre ambas
herramientas. Para implementar una prueba unitaria con
BUnit
basta seguir estos pasos:
#include "BUnit.h"
en el programa.TestCase
una clase para implementar
las pruebas.run()
las
pruebas invocando assertTrue()
.run()
para ejecutar las
pruebas.
#include "BUnit.h" // 1. Agregrar #include "BUnit.h" /// Ejemplo mínimo de uso de \c BUnit. class test0 : public TestCase { // #2. Derivar de TestCase public: bool run() { assertTrue( 1 + 1 == 3 ); // #3 Invocar assertTrue() return wasSuccessful(); } }; #include <iostream> // std::cout /// Programa principal que ejecuta la prueba. int main() { test0 test0_instance; test0_instance.run(); // #4 run(): Ejecutar las pruebas if ( ! test0_instance.wasSuccessful() ) { std::cout << test0_instance.report(); } return 0; } |
TestCase [class test0] (OK: 0) (FAIL: 1) =\_fail: 1 + 1 == 3 =/ (7) X:\DIR\SubDir\test0.cpp |
test0.cpp
El programa de la
Figura 3 muestra cómo usar el
método assertTrue()
para constatar que la
expresión booleana que recibe como argumento es verdadera.
En este caso, debido a que la suma 1+1
es diferente
de 3
, el total de pruebas erróneas queda
aumentado en 1
, hecho que queda grabado en la salida
estándar std::cout
cuando alguna prueba
unitaria no tiene éxito; en el caso contrario este programa
no grabaría nada. En la parte de abajo de la
Figura 3 aparece la hilera
de fallas que TestCase
puede construir
con base en la invocación fallida de
assertTrue()
, en donde queda indicado el
número del renglón (7)
en
donde está la prueba que ha fallado y el nombre
del archivo en donde está la falla.
Como práctica básica de programación es
saludable que la clase de prueba sea una clase amiga
(friend) de la clase con que se trabaja. Por ejemplo, en
declaración de la clase "Machine
"
estaría declarada como "friend
" la clase
"test_Machine
". Esto facilita la escritura de casos
de prueba de
caja blanca, y evita la proliferación de declaraciones
de clases amigas para la clase "Machine
".
Dice un famoso adagio que más vale una imagen que mil palabras. En el contexto de la construcción de programas lo mismo puede decirse de los ejemplos: "más vale un ejemplo que mil palabras".
bool Graph::connected( const std::string & src, const std::string & dst, std::list< std::string > & C ); |
La Figura 4 es la especificación
del método "connected()
" para encontrar la
manera de llegar a uno nodo desde otro en un grafo. Esta
especificación es bastante incompleta, pues no habla, por
ejemplo, de qué ocurre si no hay camino con la lista
"C
". Para el compilador C++ no hay problema con la
falta de una descripción, pues está claro
cuál es el tipo de cada uno de los parámetros que
este método usa. Para un programador cliente
programador cliente de la clase, es difícil adivinar
exactamente qué hace la rutina.
Encuentra un camino desde "src" hasta "dst". bool Graph::connected( const std::string & src, const std::string & dst, std::list< std::string > & C ); {{ // test::connected() assertTrue( G.isVertex( "F" ) ); // Como "F" es un vértice assertTrue( G.connected( "F" , "F", C ) ); // siempre está autoconectado assertTrue( C.size() == 1 && C.front() == "F" ); // porque ya está ahí }} |
connected()
"
Para probar la implementación del grafo es necesario
escribir algunos datos de prueba; lo que aparece en la
Figura 5 es un subconjunto de estos
datos, que sirven para que el programador que examina la
documentación de "connected()
" vea que un
vértices que sí está en el grafo siempre
está autoconectado y que, además, la longitud del
camino hacia sí mismo es 1 (el tamaño de la lista
"C
"). Si embargo, esta especificación
sería más completa si tuviera un dibujo que muestre
mejor qué hace el método
"connected()
".
Encuentra un camino desde "src" hasta "dst". bool Graph::connected( const std::string & src, const std::string & dst, std::list< std::string > & C ); {{ // test::diagram() A(1) C(1) O(1)---->O(2) / \ / \ /|\ | / \ / \ | | F->--A(2)-->-B-> ->D | | \ / \ / | | \ / \ / | \|/ A(3) C(2) O(4)<----O(3) }} {{ // test::connected() assertTrue( G.connected( "F" , "F", C ) ); // si está conectado assertTrue( C.size() == 1 && C.front() == "F" ); // porque ya está ahí assertTrue( ! G.connected( "???" , "???", C ) ); // no existe el vértice assertTrue( ! G.connected( "F" , "O(4)", C ) ); // grafo no conexo assertTrue( C.size() == 0); assertTrue( ! G.connected( "D" , "F" , C ) ); // el grafo es dirigido assertTrue( C.size() == 0); }} |
En la Figura 6 aparece un dibujo de un
grafo, el que ayuda a visualizar con mayor claridad qué
hace el método "connected()
". El contexto
visual que resulta de incluir el gráfico permite eliminar
la prueba "isVertex()
" porque al observar el dibujo
se nota que "F
" es un vértice.
El significado del verbo "assertTrue()
" es el
intuitivo: ejecuta la prueba y, en caso de que la prueba no tenga
éxito, también registra ese hecho. Las pruebas
exitosas incrementan la cantidad de éxitos, valor que puede
ser obtenido invocando el método
TestCase::successCount()
. BUnit
incluye
varias versiones del verbo "assertTrue()
", lo que le
facilita su trabajo al programador de las pruebas.
Determina si existe un camino en el grafo comenzando en "src" y terminando en "dst". * Si src == dst retorna "true" (un vértice siempre está conectado consigo mismo). * Retorna "true" cuando el camino existe, y "false" en caso contrario. * La lista "C" contiene la secuencia de nodos del camino. * Si no hay camino, la lista "C" queda vacía. bool Graph::connected( const std::string & src, const std::string & dst, std::list< std::string > & C ); {{ // test::diagram() A(1) C(1) O(1)---->O(2) / \ / \ /|\ | / \ / \ | | F->--A(2)-->-B-> ->D | | \ / \ / | | \ / \ / | \|/ A(3) C(2) O(4)<----O(3) }} {{ // test::connected() std::list< std::string > C; // camino en el grafo std::list< std::string >::iterator it; assertTrue( ! G.connected( "???" , "???", C ) ); // no existe el vértice assertTrue( ! G.connected( "F" , "O(4)", C ) ); // grafo no conexo assertTrue( C.size() == 0); assertTrue( ! G.connected( "D" , "F" , C ) ); // el grafo es dirigido assertTrue( C.size() == 0); assertTrue( G.connected( "F" , "F", C ) ); // si está conectado assertTrue( C.size() == 1 && C.front() == "F" ); // porque ya está ahí assertTrue( ! G.connected( "D", "A(2)" , C ) ); assertTrue( C.size() == 0 ); assertTrue( G.connected( "A(2)" , "D", C ) ); assertTrue( C.size() == 4 ); assertTrue( C.front() == "A(2)" && C.back() == "D" ); it = C.begin(); it++; assertTrue( *it == "B" ); // 2do nodo en el camino it++; assertTrue( *it == "C(1)" || *it == "C(2)" ); // 3er nodo en el camino }} |
En una versión posterior del programa, posiblemente existan
recursos para definir con mayor precisión la
especificación. El resultado podría ser similar a lo
que se muestra en la
Figura 7. Lo importante que aquí
se muestra es que los datos de prueba reales se han usado para
complementar la especificación. Están rodeados por
un bloque de corchetes dobles "{{ ... }}
"
para que el generador de la documentación (que en este caso
es Doxygen) pueda identificar el bloque de código que hay
que agregar a la especificación. Por supuesto, ese bloque
de código ha sido extraído del programa de prueba y
por eso es un ejemplo real, que ya ha pasado el tamizaje del
compilador, y que puede ser ejecutado efectivamente.
Es natural que los detalles de uso de una herramienta de
documentación determinen la forma en que se llega a lograr
incorporar código de prueba como parte de la
especificación. En el caso de Doxygen, esta herramienta
incluye el comando
"\dontinclude
" que sirve para copiar en la
documentación final un pedazo obtenido de otro archivo.
Para determinar adonde comienza y termina el bloque de
código hay que usar 2 hileras que lo identifiquen. Para la
Figura 7 se usaron las hileras
"test::diagram()
" y "}}
", que marcan el
principio y final del bloque de código que se quiere
incluir como parte de una especificación.
/** Determina si existe un camino en el grafo comenzando en \c "src" y terminando en \c "dst". - Si <code> src == dst </code> retorna \c "true" (un vértice siempre está conectado consigo mismo). - Retorna \c "true" cuando el camino existe, y \c "false" en caso contrario. - La lista \c "C" contiene la secuencia de nodos del camino. - Si no hay camino, la lista \c "C" queda vacía. \dontinclude test_Graph.cpp \skipline test::diagram() \until }} \skipline test::connected() \until }} \see test_Graph::test_connected() */ bool Graph::connected( const std::string & src , const std::string & dst , std::list< std::string > & C ) { // ... implementación } |
En la Figura 8 está la
codificación de la especificación en el programa
fuente. Con el comando Doxygen "\dontinclude
" se
define cuál es el archivo del que se tomará texto
para agregarlo a la documentación; en este caso el archivo
que contiene las pruebas para la clase se llama
test_Graph.cpp
.
Con los comandos "\skipline
" y "\until
"
se define la parte del archivo mencionado en
"\dontinclude
" que será incorporada en la
documentación final. Para definir el primer renglón
del bloque de código hay que identificarlo con una hilera;
para obtenerla, el truco usado es concatenar la palabra
"test
" con el nombre del método a prueba,
"connected()
" en este caso, para obtener la hilera de
identificación completa
"test::connected()
".
Con el fin de que quien usa la documentación pueda examinar
con comodidad todas las pruebas, conviene también incluir
el nombre del método que las contiene, lo que se logra con
el comando "\see
" que le indica a Doxygen que genere
un salto hacia la documentación del método
referenciado. Lo usual en estos días es navegar por la
documentación interactivamente, pero si la
documentación está siendo generada para ser impresa
en muchos casos será mejor dejar por fuera esta
referencia.
Algunos detalles menores que también hay que tomar en
cuenta son los siguientes. Es necesario especificarle a Doxygen
adónde están los archivos mencionados en el comando
"\dontinclude
": esto se logra incluyendo en
renglón
"EXAMPLE_PATH
"
del archivo de configuración. Además, con frecuencia
conviene incluir la opción
"JAVADOC_AUTOBRIEF
" que facilita la escritura de la
documentación corta de cada ítem.
Si en algún caso el archivo que se está documentando
es el mismo del hay que extraer código, caso que ocurre
cuando el archivo mencionado en el renglón
"INPUT
"
es también el utilizado para extraer documentación
con el comando "\dontinclude
" (como ocurre con
test_rational.cpp
en la
Figura 1), es importante que el texto de
ejemplo aparezca después del lugar en que aparece la
especificación, pues de lo contrario el comando
"\dontinclude
" no agregaría el texto de
ejemplo a la documentación, pues la hilera que marca el
principio del bloque a incluir aparece antes.
/// Datos de prueba para los constructores de la clase \c TestCase. void test_BUnit::test_constructor() { {{ // test::constructor() test_BUnit thisTest; assertTrue( string::npos != thisTest.getName().find( "test_BUnit" ) ); assertTrue( thisTest.failureCount() == 0 ); assertTrue( thisTest.countTestCases() == 1 ); assertTrue( thisTest.successCount() == 0 ); assertTrue( thisTest.runCount() == 0 ); assertTrue( thisTest.failureString() == "" ); }} { // Resto de las pruebas test_BUnit thisTest; assertTrue( thisTest.m_pass == 0 ); assertTrue( thisTest.m_failure == 0 ); assertTrue( thisTest.m_name == 0 ); assertTrue( thisTest.m_failureList.empty() ); } } |
TestCase
En la Figura 9 se muestra cómo
queda una prueba completa en el archivo de pruebas. Al principio
está el ejemplo que aparecerá como parte de la
documentación envuelto en el un bloque de corchetes dobles
"{{ ... }}
". Luego aparece el resto de las
pruebas, cuya existencia se justifica porque la prueba unitaria de
programas debe ser completa y, a veces, exhaustiva, pero ese
código agrega poco a la documentación. Como lo usual
es que las clase de prueba pueda ver lo privado de la clase, en
este caso el resto de la prueba
se le mete al Rep de la
clase
[DiM-2007a].
En contraste con lo que ocurre con la pareja
Doxygen-BUnit
, la mayoría de las herramientas
para prueba de programas son complicadas de entender y de usar.
Debido a que han sido diseñadas para ambientes de trabajo
especiales, orientados al uso de metodologías de
programación "ágiles" como XP, es poco frecuente
encontrarles uso o espacio en ambientes académicos. En
consecuencia, los alumnos pasan por la universidad acostumbrados a
"probar mañana y nunca hoy", lo que efectivamente resulta
en un significativo problema de formación. Por eso, es
necesario contar con una herramienta simple que le sirva a los
alumnos, de manera que al usarla logren aumentar su productividad
al mismo tiempo que incorporan en su diario quehacer la disciplina
de primero probar y luego codificar.
Los estudiantes que han sido expuestos a la pareja
Doxygen-BUnit
con frecuencia no lo notan. En lugar de
recalcarles su obligación de incluir prueba unitaria de
programas, más bien se les menciona que deben completar con
ejemplos su documentación. Además, en aquellos casos
en que un proyecto consiste en implementar una parte de una clase,
o un grupo de rutinas o métodos, la definición del
problema incluye los casos de prueba como documentación, lo
que también usan al construir su solución al
proyecto. Debido a que documentar es "feo" para los programadores,
hacer casos de prueba se transforma en algo "bonito", pues ese
trabajo es percibido como programación y no como
documentación. Esto ayuda mucho a que el trabajo de
realizar pruebas sea realmente provechoso y desafiante, y no
aburrido por ser obligatorio. En otras palabras, el uso de estas 2
herramientas resulta natural a los estudiantes quienes no gastan
esfuerzo en luchar contra su uso sino que, más bien, de
manera natural lo incorporan a su trabajo. En muchas ocasiones es
útil no incluir palabras elevadas, como "prueba unitaria",
"programación extrema" o "especificación", de manera
que los muchachos no tienen que lidiar con conceptos abstractos
sino que más bien es con la práctica que llegan a
incorporar la buena disciplina de documentar módulos usando
prueba unitaria de programas.
El verbo assertTrue()
está implementado como
una macro C++. Mediante el uso de las macros predefinidas del
compilador
__LINE__
y
__FILE__
, junto con el operador
# (stringify)
, que permite obtener el texto de la
prueba que se usa como argumento en
assertTrue()
, se logra crear la hilera de
falla junto con la mención del renglón
en donde se produjo la falla. Esta
implementación parece poco elegante, pues los
programadores C++ evitan siempre que pueden el uso de
macros, pero parece inevitable para implementar
BUnit
.
#include "BUnit.h" // 1. Agregrar #include "BUnit.h" /// Ejemplo mínimo de uso de \c BUnit. class test0 : public TestCase { // #2. Derivar de TestCase public: bool run() { assertTrue( 1 + 1 == 3 ); // #3 Invocar assertTrue() return wasSuccessful(); } }; |
#include "BUnit.h" // 1. Agregrar #include "BUnit.h" /// Ejemplo mínimo de uso de \c BUnit. class test0 : public TestCase { // #2. Derivar de TestCase public: bool run() { // #3 Invocar assertTrue() en realidad invoca testThis() testThis( (1 + 1 == 3) , "1 + 1 == 3" , __FILE__ , __LINE__); return wasSuccessful(); } }; |
testThis()
en lugar de assertTrue()
La macro assertTrue()
sirve para invocar al
método testThis()
que se encarga de realizar
la prueba y de registrar su éxito o fracaso. Como
assertTrue()
es una macro, genera una hilera que
contiene la condición boolean que hay que evaluar
testThis()
y además incluye la
indicación del renglón y el archivo en donde
está la invocación, lo que sirve después para
reportar las pruebas que fallan.
En la Figura 10 se muestra cómo
el programador cliente de BUnit
puede usar
directamente el método testThis()
para hacer
una prueba. Es incómodo hacerlo sin usar la macro
assertTrue()
pues hay que agregar varios
parámetros que contienen los datos que hay que registran
para las pruebas que no tienen éxito.
#define assertTrue( CONDITION ) \ testThis( CONDITION, #CONDITION, __FILE__, __LINE__ ) |
inline void TestCase::testThis( bool cond, // (1 + 1 == 3) const char * label, // "1 + 1 == 3" const char * fname, // __FILE__ long lineno, // __LINE__ bool must_copy ) { if (cond) { recordSuccess(); } else { recordFailure( label, fname, lineno, must_copy ); } } |
testThis()
y de assertTrue()
En la implementación de assertTrue()
que se
muestra en la
Figura 11 se puede visualizar
cómo se usa el preprocesador C++ para obtener los datos que
testThis()
debe registrar. Por eso, si el programador
cliente lo necesitara, podría construir la
hilera de falla para lograr definir mejor por
qué la prueba no fue exitosa. Como alternativa,
también puede usar alguna de las otras
versiones de assertTrue()
que le permiten
lograr lo mismo sin necesidad de invocar directamente
testThis()
. Pese a lo rudimentario del
método a fin de cuentas el programador cliente
tiene un muy buen control al construir sus programas
de prueba unitaria utilizando BUnit
.
bool run() { for ( int i=0; i<N; ++i ) { for ( int j=0; j<N; ++j ) { std::string err = "m_Matrix"; err += '[' + TestCase::toString(i) + ']'; err += '[' + TestCase::toString(j) + ']'; err += " == 0"; assertTrue_Msg( err , m_Matrix[i][j] == 0 ); } } return wasSuccessful(); } |
assertTrue_Msg()
En la Figura 12 se muestra cómo
puede el programador cliente lograr que el mensaje de falla
incluya información adicional que con el uso de
assertTrue()
se perdería. En este caso, en
lugar de obtener el mensaje de falla genérico
m_Matrix[i][j] == 0
,
que indica que una prueba no tuvo éxito posición
"i-j" de la matriz, se obtiene un mensaje más descriptivo
que incluye la referencia exacta de la casilla en donde la prueba
no tuvo éxito (en este caso 317-254):
=\_fail: m_Matrix[317][254] == 0 =/ (31) X:\DIR\SubDir\test1.cpp
Para mejorar la posibilidad de que quien aprende con
BUnit
pueda luego usar también con comodidad
tanto JUnit
como CppUnit
, en la
implementación se han incluido varias versiones del verbo
assertTrue()
que facilitan su uso. Algunas de las más
importantes son éstas:
assertTrue(condition)
assertTrue( condition )
CPPUNIT_ASSERT( condition )
assertTrue_Msg( message, condition )
assertTrue( message, condition )
CPPUNIT_ASSERT_MESSAGE( message, condition )
assertEquals( expected, actual )
assertEquals( expected, actual )
CPPUNIT_ASSERT_EQUAL( expected, actual )
assertFalse( condition )
assertFalse( condition )
CPPUNIT_ASSERT( !(condition) )
fail_Msg( message )
fail( message )
y también
fail()
CPPUNIT_FAIL( message )
Lograr que toda la implementación de BUnit
resida en un único archivo de encabezado requirió de varias contorsiones programáticas. Debido a que toda la implementación está contenida en el archivo
"BUnit.h
",
lo único que un programador necesita para construir su
prueba unitaria de programas es agregar lo con la directiva:
#include "BUnit.h"
Lo usual al implementar una clase C++ es usar 2 archivos: el de
encabezado de extensión ".h
" y el de
implementación, de extensión ".cpp
".
Sin embargo, cuando se usan plantillas, toda la
implementación debe estar en el archivo de encabezado. Los
compiladores modernos se encargan de resolver los conflictos que
ocurren cuando el mismo archivo de encabezado queda, completo, en
2 o más archivos de implementación. Esta ayuda
adicional del compilador es lo que motivó a implementar la
clase
"TestSuite
"
usando plantillas. Para hacer más fácil la lectura
del código se usó el identificador
"TestCase
" al implementar la plantilla
"TestSuite
"; esto obligó a una
contorsión extraña, cual es crear un tipo
sinónimo de "TestCase
". De esta forma, es
posible informarle al compilador cuál es la clase de la que
deriva "TestSuite
" al mismo tiempo que se usa es
clase como nombre del parámetro de la plantilla.
Debido a que la mayor parte de los métodos de la clase
"TestCase
" son muy simples, casi todos fueron
implementados como métodos "inline"; por eso no es
necesario implementar "TestCase
" con plantillas.
Para almacenar las pruebas que no tuvieron éxito se usa una
lista, la que contiene las fallas almacenadas en valores
"TestCaseFailure
". Esta clase presenta una
particularidad adicional, pues en algunos casos contiene hileras
que deben ser destruidas por el destructor de la case. Lo usual es
que las hileras de falla sean generadas por el compilador como
hileras constantes, por lo que destruirlas es una
equivocación. En otros casos, ocurre que la hilera se
obtiene después de hacer varias operaciones con objetos
std::string
, en cuyo caso el valor almacenado
sí debe ser destruido. Para manejar este caso particular,
en la clase "TestCaseFailure
" se guarda una
indicación de cuándo es necesario destruir la hilera
asociada a una falla: esto explica por qué es necesario el
parámetro "must_copy
" del método
"testThis()
".
std::string TestCase::toString( const T & val ) { // typedef basic_ostringstream<char> ostringstream; std::basic_ostringstream<char> temp; // ostringstream temp; temp << val; return temp.str( ); } |
TestCase::toString()
Al escribir las pruebas muchas veces aparece la tentación
de imprimir algo. Martin Fowler dijo una vez
[Fowler-1999]: "Siempre que tenga la
tentación de imprimir algo o de usar una expresión
para el depurador simbólico, más bien escriba un
caso de prueba". De esa manera se logra aprovechar el esfuerzo
realizado para perpetuarlo como un módulo que verifica la
correctitud del programa. Para apoyar esta buena práctica
conviene contar con una función que convierta su argumento
en una hilera usando los operadores de flujos: este es el trabajo
que realiza el método estático
TestCase::toString()
, que usa un flujo básico
como intermediario para producir la hilera resultado (ver
Figura 13). Como este es un
método estático no se poluciona el espacio de
nombres del programador cliente; otra forma menos elegante de
lograr lo mismo es usar un nuevo "namespace
" para
lograr lo mismo.
Para obtener dinámicamente el nombre de la clase en la
implementación de BUnit
se usa
RTTI
(Run time type identification). Algunas veces esto
produce errores de compilación si el compilador C++ no
está configurado para incluir información RTTI en el
programa objeto, pero esta limitante no fue considerada suficiente
como para desistir de usar esta facilidad sintáctica del
lenguaje C++.
Para simplificar BUnit
no se hace diferencia entre un
caso de prueba de prueba exitoso y uno que no tiene éxito
porque no se ha levantado la excepción adecuada. Esto
contrasta con JUnit
, que llama "falla" a un caso de
prueba que ha levantado la excepción equivocada. Sin
embargo, sí es posible usar BUnit
para
registrar que las excepciones han sido manejadas adecuadamente,
por lo que no hay que incluir código especial para el
manejos de bloques "try
".
Puede que en el futuro sea conveniente agregarle a
BUnit
la clase
TestResult
, que sirve para recolectar los resultados
de cada prueba de manera que luego pueda ser presentada de varias
formas diferentes. De esta forma, el rol que cumple la clase
TestCase
sería muy específico, pues la
responsabilidad de recolectar los resultados del las pruebas
recaería en la clase TestResult
. Sin embargo,
aunque desde un punto de vista arquitectónico es agradable
esa separación de responsabilidades, también
requiere del usuario de BUnit
digerir un concepto
adicional, lo que puede limitar su aceptación. Por eso,
para cumplir lo mejor posible con el principio de "simple es
bello", la clase TestResult
queda relegada a otros
miembros de la familia xUnit
. También por eso
es relativamente complicado agregarle a BUnit
una
"barra de progreso". Otros enfoques son posibles, como el expuesto
en
[VGS-2003].
En algunos casos podría resulta conveniente que los
métodos setUp()
y tearDown()
estuvieran declaradas en una clase de la que TestCase
derive, de manera que la jerarquía de clases fuera
ésta:
TestFixture
→TestCase
→TestSuite<TestCase>
.
De nuevo, para simplificar las cosas, se escogió dejar esos
métodos en la clase TestCase
.
Doxygen es una herramienta que es fácil usar. Si se toma un archivo de configuración similar al que se muestra en la Figura 1 y se examinan algunos ejemplos específicos, como los mostrados en la Figura 8, es posible obtener automáticamente una documentación adecuada para programas y módulos. Explicaciones y contorsiones complicadas no necesarias al usar Doxygen.
Al aplicar la técnica descrita en este artículo es
posible incorporar en la especificación ejemplos de uso
tomados del programa de prueba unitaria. La técnica
consiste en definir un bloque de código ejemplo usando
marcadores Doxygen
"\dontinclude
",
"\skipline
" y
"\until
" como se muestra en la
Figura 8, en el que el código de
ejemplo está encerrado en un bloque de corchetes dobles
"{{ ... }}
". Este bloque debe estar
identificado por una hilera única que marca la prueba. La
hilera de identificación se construye al concatenar la
palabra "test
" con el nombre del método a
prueba: "test::connected()
".
Los ejemplos de uso que acompañan a este documento son suficientemente completos para que quien los examine mejore la especificación de módulos incorporándoles los datos de prueba reales, tomados de programas de prueba funcionales. Esta mejora es sustancial si el ambiente de trabajo es uno en el que la documentación es una prioridad secundaria o no existe. Así se logra que la prueba unitaria de programas también sea un vehículo para aprender a mejorar la calidad de los programas.
Muchas herramientas de la familia xUnit
son
más completas que BUnit
. Sin embargo, dado su
diseño simple, BUnit
tiene la ventaja de que
se puede comenzar a usar inmediatamente sin necesidad de aprender
mucho detalle. Debido a que los verbos usados en
BUnit
son similares a los de JUnit
, el
aprendizaje de nuevas herramientas similares se facilita mucho,
por lo que BUnit
puede ser una alternativa viable
para introducir en un ambiente profesional el uso de este tipo de
herramienta. Siempre es muy importante que el diseño sea
sencillo (diseñar es definir qué es lo importante,
no incluir siempre todas las posibles opciones).
Receta:
(1)
Verifique la documentación generada por Doxygen.
(2)
Refine las pruebas que deben aparecer en la
documentación.
(3)
Mejore las pruebas para que sean completas o exhaustiva.
Alejandro Di Mare aportó varias observaciones y sugerencias importantes para mejorar este trabajo.
BUnit.zip
: Todos los fuentes
[.zip]
http://www.di-mare.com/adolfo/p/BUnit/BUnit.zip
BUnit.h
: Documentación general
[.html]
[.h]
[.txt]
http://www.di-mare.com/adolfo/p/BUnit/es/index.html
test_BUnit.cpp
: Prueba BUnit
[.html]
[.cpp]
[.txt]
http://www.di-mare.com/adolfo/p/BUnit/es/classtest__BUnit.html
TestCase
:
Cada caso de prueba es una instancia derivada de esta clase abstracta
http://www.di-mare.com/adolfo/p/BUnit/es/classTestCase.html
TestSuite
: Colección de pruebas
http://www.di-mare.com/adolfo/p/BUnit/es/classTestSuite.html
BUnit.h
: General documentation
[.html]
[.h]
[.txt]
http://www.di-mare.com/adolfo/p/BUnit/en/index.html
test_BUnit.cpp
: Testing BUnit
[.html]
[.cpp]
[.txt]
http://www.di-mare.com/adolfo/p/BUnit/en/classtest__BUnit.html
TestCase
:
Each test case is an instance derived from this abstract class
http://www.di-mare.com/adolfo/p/BUnit/en/classTestCase.html
TestSuite
: Test collection
http://www.di-mare.com/adolfo/p/BUnit/en/classTestSuite.html
test0.cpp
: Ejemplo mínimo de uso de BUnit
[.html]
[.cpp]
[.txt]
http://www.di-mare.com/adolfo/p/BUnit/es/test0_8cpp_source.html
test1.cpp
: Muestra de uso de assertTrue_Msg()
[.html]
[.cpp]
[.txt]
http://www.di-mare.com/adolfo/p/BUnit/es/test1_8cpp_source.html
rational<INT>
:
Operaciones aritméticas para números racionales
[.html]
[.h]
[.txt]
http://www.di-mare.com/adolfo/p/BUnit/es/classrational.html
test_rational.cpp
: Prueba rational<INT>
[.html]
[.cpp]
[.txt]
http://www.di-mare.com/adolfo/p/BUnit/es/classtest__rational.html
ADH_Graph
:
Versión muy simplificada de un grafo
[.h.html.cpp]
[.h][.cpp]
[.h.txt.cpp]
http://www.di-mare.com/adolfo/p/BUnit/es/classADH_1_1Graph.html
ADH_Graph_Lib.cpp
:
Funciones de apoyo para ADH_Graph.h
[.h.html.cpp]
[.h][.cpp]
[.h.txt.cpp]
http://www.di-mare.com/adolfo/p/BUnit/es/ADH__Graph__Lib_8h.html
http://www.di-mare.com/adolfo/p/BUnit/es/ADH__Graph__Lib_8cpp.html
test_Graph.cpp
: Prueba Graph
[.html]
[.cpp]
[.txt]
http://www.di-mare.com/adolfo/p/BUnit/es/classADH_1_1test__Graph.html
ftp://ftp.stack.nl/pub/users/dimitri/doxygen-1.5.6-setup.exe
[1] | En Costa Rica existió una crema, llamada "Pomada Canaria", a la que se le atribuyeron propiedades curativas totales. Por eso, a cualquier solución que sea presentada como total, completa y absoluta se le equipara con la mítica pomada, que nunca tuvo todas las propiedades que los ticos le atribuían. Alguien ha afirmado que: "La "pomada canaria" que vendía un español en la avenida central, creó el dicho de "creerse la pomada canaria", o sea, creerse capaz de curar, hacer y decir todo". |
[2] | En Costa Rica se les llama "curitas" a las cintas adhesivas medicadas que se usan para curar heridas cutáneas pequeñas. Posiblemente el nombre viene de la marca de las curitas que primero se usaron en el país, de marca "Cure Aid". |
[3] | En Costa Rica el sufijo "tico"" es un diminutivo bastante utilizado. Por eso, a los costarricenses coloquialmente se nos llama "ticos". |
|
|
[Allison-2000] | Allison, Chuck:
The Simplest Automated Unit Test Framework That Could Possibly Work,
C/C++ Users Journal, 18, No.9, Sep 2000.
http://www.ddj.com/cpp/184401279
|
[Beck-1999] | Beck, Kent: eXtreme Programming Explained, Addison Wesley, Reading, MA, USA, 1999. |
[Beck-2002] | Beck, Kent: Test driven development, Addison Wesley, Reading, MA, USA, 2002. |
[BG-1998] | Beck, Kent & Gamma, Erich:
JUnit Test Infected: Programmers Love Writing Tests,
Java Report, 3(7):37-50, 1998.
http://junit.sourceforge.net/doc/testinfected/testing.htm
|
[Boehm-1981] | Boehm, Barry W.: Software Engineering Economics, (Prentice-Hall Advances in Computing Science & Technology Series), 1981. |
[DiM-2007a] | Di Mare, Adolfo:
¡No se le meta al Rep!,
Reporte Técnico ECCI-2007-01,
Escuela de Ciencias de la Computación e Informática,
Universidad de Costa Rica,
2007.
http://www.di-mare.com/adolfo/p/Rep.htm
|
[DiM-2007b] | Di Mare, Adolfo:
Uso de Doxygen para especificar módulos y programas,
Reporte Técnico ECCI-2007-02,
Escuela de Ciencias de la Computación e Informática,
Universidad de Costa Rica,
2007.
http://www.di-mare.com/adolfo/p/Doxygen.htm
|
[FLetc-2007] | Feathers, Michael
&
Lacoste, Jerome & etc.:
CppUnit,
2007.
http://cppunit.sourceforge.net
|
[Fowler-1999] | Fowler, Martin:
Refactoring: Improving the Design of Existing code,
Addison-Wesley Co., Inc, Reading, MA, 3rd printing Nov 1999.
http://www.refactoring.com
|
[Llopis-2004] | Llopis, Noel:
Exploring the C++ Unit Testing Framework Jungle,
Games From Within, 2004.
http://www.gamesfromwithin.com/articles/0412/000061.html
|
[Martin-1986] | Martin, James: Fourth Generation Languages, Prentice-Hall, ISBN 978-0133297492, 1986. |
[Myers-2004] | Myers, Glenford J.: The Art of Software Testing 2nd Ed, John Wiley & Sons, Inc., 2004. |
[Sommerlad-2006] | Sommerlad, Peter:
C++ Unit Testing Easier: CUTE,
ACCU Overload Journal #75, Oct 2006.
http://accu.org/index.php/journals/1349
http://wiki.hsr.ch/PeterSommerlad/
|
[VGS-2003] | Venners, Bill with Gerrans, Matt & Sommers, Frank:
Why We Refactored Junit: The Story of an Open Source Endeavor,
Artima developer,
2003.
http://www.artima.com/suiterunner/why.html
|
[VH-2005] | van Heesch, Dimitri:
Doxygen,
2005.
http://www.doxygen.org/index.html
|
[-] | Resumen |
[1] | Introducción |
[2] | Doxygen es parte de la solución |
[3] | Formar a los jóvenes para convencer a los viejos |
[4] | Probar y especificar antes de codificar |
[5] | Arquitectura de BUnit
|
[6] | Especificación por medio de prueba de programas |
[7] | Formato de la documentación Doxygen |
[8] | Experiencia con estudiantes |
[9] | Detalles de implementación |
[10] | Posibilidades de ampliación |
[11] | Conclusiones |
[12] | Agradecimientos |
[13] | Código fuente |
|
|
Bibliografía | |
Indice | |
Acerca del autor | |
Acerca de este documento | |
Principio Indice Final |
Adolfo Di Mare: Investigador costarricense en la Escuela de Ciencias de la Computación e Informática [ECCI] de la Universidad de Costa Rica [UCR], en donde ostenta el rango de Profesor Catedrático. Trabaja en las tecnologías de Programación e Internet. Es Maestro Tutor del Stvdivm Generale de la Universidad Autónoma de Centro América [UACA], en donde ostenta el rango de Catedrático. Obtuvo la Licenciatura en la Universidad de Costa Rica, la Maestría en Ciencias en la Universidad de California, Los Angeles [UCLA], y el Doctorado (Ph.D.) en la Universidad Autónoma de Centro América. |
Adolfo Di Mare: Costarrican Researcher at the Escuela de Ciencias de la Computación e Informática [ECCI], Universidad de Costa Rica [UCR], where he is full professor. Works on Internet and programming technologies. He is Tutor at the Stvdivm Generale in the Universidad Autónoma de Centro América [UACA], where he is Cathedraticum. Obtained the Licenciatura at UCR, and the Master of Science in Computer Science from the University of California, Los Angeles [UCLA], and the Ph.D. at the Universidad Autónoma de Centro América. |
Referencia: | Di Mare, Adolfo:
BUnit.h : Un módulo simple para aprender prueba unitaria de programas en C++
,
X Simposio Internacional de Informática Educativa
(SIIE'08)
realizado del 1 al 3 de octubre 2008, Salamanca, España,
I.S.B.N.: 978-84-7800-312-9, pp425-430, octubre 2008.
|
Internet: |
http://www.di-mare.com/adolfo/p/BUnit.htm
Google™
Translate
|
Versión SIIE'08: |
http://www.di-mare.com/adolfo/p/BUnit-SIIE-2008.pdf
Google™
Translate
http://www.di-mare.com/adolfo/p/BUnit-SIIE-2008.ppt
Google™
Translate
http://siie08.usal.es
|
Autor: | Adolfo Di Mare
<adolfo@di-mare.com>
|
Contacto: | Apdo 4249-1000, San José Costa Rica Tel: (506) 2511-8000 Fax: (506) 2438-0139 |
Revisión: | ECCI-UCR, Enero 2008 |
Visitantes: |