[UCR]  
[/\]
Universidad de Costa Rica
Escuela de Ciencias de la
Computación e Informática
[<=] [home] [<>] [\/] [=>]
Google Translate

BUnit.h: Un módulo C++ simple para aprender prueba unitaria de programas

Adolfo Di Mare




Resumen [<>] [\/] [/\]

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.

Versión SIIE'08 --- BUnit-SIIE-2008.pdf --- BUnit-SIIE-2008.ppt


Introducción [<>] [\/] [/\]

      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:


Doxygen es parte de la solución [<>] [\/] [/\]

      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.

PROJECT_NAME          = "Prueba de la clase rational:"
OUTPUT_LANGUAGE       = Spanish
OUTPUT_DIRECTORY      = .
EXAMPLE_PATH          = .
GENERATE_LATEX        = NO
GENERATE_MAN          = NO
GENERATE_RTF          = NO
CASE_SENSE_NAMES      = YES
INPUT_ENCODING        = ISO-8859-1
INPUT                 = rational.h test_rational.cpp
RECURSIVE             = NO
QUIET                 = YES
JAVADOC_AUTOBRIEF     = YES
EXTRACT_ALL           = YES
EXTRACT_PRIVATE       = YES
EXTRACT_STATIC        = YES
EXTRACT_LOCAL_CLASSES = YES
INLINE_INHERITED_MEMB = YES
SOURCE_BROWSER        = YES
INLINE_SOURCES        = NO
STRIP_CODE_COMMENTS   = NO
REFERENCED_BY_RELATION= NO
REFERENCES_RELATION   = NO
FULL_PATH_NAMES       = NO

SORT_MEMBER_DOCS      = NO
SORT_BRIEF_DOCS       = NO
CLASS_DIAGRAMS        = YES

ENABLE_PREPROCESSING  = YES
MACRO_EXPANSION       = YES
EXPAND_ONLY_PREDEF    = YES
PREDEFINED            = "DOXYGEN_COMMENT" \
                        "_MSC_VER=1300"

EXAMPLE_PATH          = .

#--- TODOS ESTOS SON MENOS COMUNES ---
# DISTRIBUTE_GROUP_DOC = YES
# ENABLE_PREPROCESSING = YES
# FILE_PATTERNS        = diagrams_*.h
# GENERATE_TAGFILE     = example.tag
# HAVE_DOT             = YES
# PERL_PATH            = perl
# TAGFILES             = example.tag=../../example/html

# Manual ==> http://www.doxygen.org/manual.html
Figura 1: 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.


Formar a los jóvenes para convencer a los viejos [<>] [\/] [/\]

      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.


Probar y especificar antes de codificar [<>] [\/] [/\]

      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].


Arquitectura de 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 & ) |
+-------------------------+
Figura 2: Clases de 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:

  1. Agregrar #include "BUnit.h" en el programa.
  2. Derivar de TestCase una clase para implementar las pruebas.
  3. Implementar en el método run() las pruebas invocando assertTrue().
  4. Usar el método 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
Figura 3: El programa de prueba 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".


Especificación por medio de prueba de programas [<>] [\/] [/\]

      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
);
Figura 4: Encuentra un camino desde "src" hasta "dst"

      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í
}}
Figura 5: Especificación mejorada para el método "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);
}}
Figura 6: Especificación mejorada con un dibujo

      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
}}
Figura 7: Especificación completa

      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.


Formato de la documentación Doxygen [<>] [\/] [/\]

      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
}
Figura 8: Comentarios Doxygen que resultan en la especificación de la Figura 7

      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() );
    }
}
Figura 9: Implementación de las pruebas para el constructor de la clase 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].


Experiencia con estudiantes [<>] [\/] [/\]

      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.


Detalles de implementación [<>] [\/] [/\]

      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();
    }
};
Figura 10: Uso de 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 );
    }
}
Figura 11: Implementación de 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();
}
Figura 12: Afinamiento de los mensajes por medio de 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)
JUnit assertTrue( condition )
CppUnit CPPUNIT_ASSERT( condition )
assertTrue_Msg( message, condition )
JUnit assertTrue( message, condition )
CppUnit CPPUNIT_ASSERT_MESSAGE( message, condition )
assertEquals( expected, actual )
JUnit assertEquals( expected, actual )
CppUnit CPPUNIT_ASSERT_EQUAL( expected, actual )
assertFalse( condition )
JUnit assertFalse( condition )
CppUnit CPPUNIT_ASSERT( !(condition) )
fail_Msg( message )
JUnit fail( message ) y también fail()
CppUnit 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( );
}
Figura 13: Implementación de 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 ".


Posibilidades de ampliación [<>] [\/] [/\]

      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: TestFixtureTestCaseTestSuite<TestCase>. De nuevo, para simplificar las cosas, se escogió dejar esos métodos en la clase TestCase.


Conclusiones [<>] [\/] [/\]

      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.


Agradecimientos [<>] [\/] [/\]

      Alejandro Di Mare aportó varias observaciones y sugerencias importantes para mejorar este trabajo.




Código fuente [<>] [\/] [/\]

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

Disponibilidad Doxygen:
ftp://ftp.stack.nl/pub/users/dimitri/doxygen-1.5.6-setup.exe


Notas de pie de página [<>] [\/] [/\]

[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".

Bibliografía [<>] [\/] [/\]


           
[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

Indice [<>] [\/] [/\]

[-] 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

Acerca del autor [<>] [\/] [/\]

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.
[mailto]Adolfo Di Mare <adolfo@di-mare.com>

Acerca de este documento [<>] [\/] [/\]

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:


Copyright © 2008 Adolfo Di Mare
Derechos de autor reservados © 2008 Adolfo Di Mare <adolfo@di-mare.com>
[home] [<>] [/\]