Pruebas y pruebas unitarias

main1.png main2.png main3.png

Como habrás aprendido en experiencias de laboratorio anteriores, lograr que un programa compile es solo una pequeña parte de programar. El compilador se encargará de decirte si hubo errores de sintaxis, pero no podrá detectar errores en la lógica del programa. Es muy importante probar las funciones del programa para validar que producen los resultados correctos y esperados.

Una manera de hacer estas pruebas es a mano; esto es, corriendo el programa múltiples veces, ingresando valores representativos (por medio del teclado) y visualmente verificando que el programa devuelve los valores esperados. Otra forma más conveniente es implementar funciones dentro del programa cuyo propósito es verificar que otras funciones produzcan resultados correctos. En esta experiencia de laboratorio practicarás ambos métodos de verificación.

Objetivos:

  1. Validar el funcionamiento de varias funciones haciendo pruebas "a mano".
  2. Crear pruebas unitarias para validar funciones, utilizando la función assert

Pre-Lab:

Antes de llegar al laboratorio debes:

  1. Haber repasado los conceptos básicos relacionados a pruebas y pruebas unitarias.

  2. Haber repasado el uso de la función assert para hacer pruebas.

  3. Haber estudiado los conceptos e instrucciones para la sesión de laboratorio.

  4. Haber tomado el quiz Pre-Lab disponible en Moodle.



Haciendo pruebas a una función.

Cuando probamos la validez de una función debemos probar casos que activen los diversos resultados de la función.


Ejemplo 1: Si fueras a validar una función esPar(unsigned int n) que determina si un entero positivo n es par, deberías hacer pruebas a la función tanto con números pares como números impares. Un conjunto adecuado de pruebas para dicha función podría ser:

Prueba Resultado esperado
esPar(8) true
esPar(7) false

Ejemplo 2: Digamos que un amigo ha creado una función unsigned int rangoEdad(unsigned int edad) que se supone que devuelva 0 si la edad está entre 0 y 5 (inclusivo), 1 si la edad está entre 6 y 18 (inclusivo), y 2 si la edad es mayor de 18. Una fuente común de errores en funciones como esta son los valores próximos a los límites de cada rango. Por ejemplo, el número 5 se presta para error si el programador no usó una comparación correcta. Un conjunto adecuado de pruebas para la función rangoEdad sería:

Prueba Resultado esperado
rangoEdad(5) 0
rangoEdad(2) 0
rangoEdad(6) 1
rangoEdad(18) 1
rangoEdad(17) 1
rangoEdad(19) 2
rangoEdad(25) 2

La función assert

La función assert(bool expression) se puede utilizar como herramienta rudimentaria para validar funciones. assert tiene un funcionamiento muy sencillo y poderoso. Si la expresión que colocamos entre los paréntesis de assert es cierta la función permite que el programa continúe con la próxima instrucción. De lo contrario, si la expresión que colocamos entre los paréntesis es falsa, la función assert hace que el programa termine e imprima un mensaje al terminal que informe al usuario sobre la instrucción de assert que falló.

Por ejemplo, el siguiente programa correrá de principio a fin sin problemas pues todos las expresiones incluidas en los paréntesis de los asserts evalúan a cierto.


#include <iostream>
#include <cassert>
using namespace std;

int main() {
   int i = 10, j = 15;
   assert(i == 10);
   assert(j == i + 5);
   assert(j != i);
   assert( (j < i) == false);
   cout << "Eso es todo, amigos!" << endl;
   return 0;
}

Figura 1. Ejemplo de programa que pasa todas las pruebas de assert.


El siguiente programa no correrá hasta el final pues el segundo assert (assert(j == i);) contiene una expresión (j == i) que evalúa a falsa.



#include <iostream>
#include <cassert>
using namespace std;

int main() {
   int i = 10, j = 15;
   assert(i == 10);
   assert(j == i);
   assert(j != i);
   assert( (j < i) == false);
   cout << "Eso es todo, amigos!" << endl;
   return 0;
}

Figura 2. Ejemplo de programa que no pasa una prueba de assert.


Al correr el pasado programa, en lugar de obtener la frase "Eso es todo amigos!” en el terminal, obtendremos un mensaje como el siguiente:

Assertion failed: (j == i), function main, file ../programa01/main.cpp, line 8.

El programa no ejecuta más instrucciones después de la línea 8.

¿Cómo usar assert para validar funciones?

Digamos que deseas automatizar la validación de la función rangoEdad. Un forma de hacerlo es crear una función que llame a la función rangoEdad con diversos argumentos y verifique que lo devuelto concuerde con el resultado esperado. Si incluimos cada comparación entre lo devuelto por rangoEdad, y el resultado esperado dentro de un assert, obtenemos una función que se ejecuta de principio a fin solo si todas las invocaciones devolvieron el resultado esperado.


void test_rangoEdad() {
   assert(rangoEdad(5) == 0);
   assert(rangoEdad(2) == 0);
   assert(rangoEdad(6) == 1);
   assert(rangoEdad(18) == 1);
   assert(rangoEdad(17) == 1);
   assert(rangoEdad(19) == 2);
   assert(rangoEdad(25) == 2);
   cout << "rangoEdad passed all tests!!!" << endl;
}

Figura 3. Ejemplo de una función para pruebas usando assert.



La función esPar(int n) devuelve true si n es par. ¿Cuál(es) serían asserts recomendables para una función que valide la función bool esPar(unsigned int)? assert( esPar(8) == true); assert( esPar(11) == false); assert( esPar(2) == true); assert( esPar(7) == true); Todas las opciones menos la última sería recomendables. La última opción es una prueba mal formulada pues está verificando que el resultado de esPar(7) sea true.

Una amiga te provee una función llamada int roundIt(double n) que dado un número de tipo double en el rango de -1000.0 a +1000.0 devuelve el número redondeado al entero más cercano. Otro amigo te provee la siguiente función para validar la función roundIt:


Al correr la función, el programa se detiene en la línea 28 y nos reporta que la aserción no se cumplió. ¿Qué podemos concluir?
La función roundIt no tiene un resultado válido para ciertos valores de argumentos. La función test_roundIt contiene una prueba mal formulada. En este caso, lo único que podemos decir con certeza es que la función test_roundIt contiene una prueba mal formulada. La línea 28 compara el resultado de test_roundIt(10.4) con 11 lo cual no es el resultado esperado de la función, el resultado de test_roundIt(10.4) debe ser 10, no 11.



Sesión de laboratorio:

Ejercicio 1: Diseñar pruebas "a mano"

En este ejercicio practicarás cómo diseñar pruebas para validar funciones, utilizando solamente la descripción de la función y el interfaz gráfico que se usa para interactuar con la función.

El ejercicio NO requiere programación, solo requiere que entiendas la descripción de la función, y tu habilidad para diseñar pruebas. Este ejercicio y el Ejercicio 2 son una adaptación de la actividad descrita en [1].

Ejemplo 3. Supón que una amiga te provee un programa. Ella asegura que el programa resuelve el siguiente problema:

"dados tres enteros, despliega el valor máximo".

Supón que el programa tiene una interfaz como la siguiente:


figure4.png

Figura 4. Interfaz de un programa para hallar el valor máximo entre tres enteros.


Podrías determinar si el programa provee resultados válidos sin analizar el código fuente. Por ejemplo, podrías intentar los siguientes casos:

  • a = 4, b = 2, c = 1; resultado esperado: 4
  • a = 3, b = 6, c = 2; resultado esperado: 6
  • a = 1, b = 10, c = 100; resultado esperado: 100

Si alguno de estos tres casos no da el resultado esperado, el programa de tu amiga no funciona. Por otro lado, si los tres casos funcionan, entonces el programa tiene una alta probabilidad de estar correcto.

Funciones para validar

En este ejercicio estarás diseñando pruebas que validen varias versiones de las funciones que se describen abajo. Cada una de las funciones tiene cuatro versiones, "Alpha", "Beta", "Gamma" y "Delta".

  • 3 Sorts: Una función que recibe tres "strings" y los ordena en orden lexicográfico (alfabético). Por ejemplo, dados jirafa, zorra, y coqui, los ordena como: coqui, jirafa, y zorra. Para simplificar el ejercicio, solo usaremos "strings" con letras minúsculas. La Figura 5 muestra la interfaz de esta función. Nota que hay un menú para seleccionar la versión implementada.


    figure5.png

    Figura 5. Interfaz de la función 3 Sorts.


  • Dice: Cuando el usuario marca el botón Roll them!, el programa genera dos enteros aleatorios entre 1 y 6. El programa informa la suma de los enteros aleatorios.


    figure6.png

    Figura 6 - Interfaz de la función Dice.


  • Rock, Paper, Scissors: Cada uno de los jugadores entra su jugada y el programa informa quién ganó. La Figura 7 muestra las opciones en las que un objeto le gana a otro. La interfaz del juego se muestra en la Figura 8.


    figure7.jpg

    Figura 7. Formas de ganar en el juego "Piedra, papel y tijera".


    figure8.png

    Figura 8. Interfaz de la función Rock, Paper, Scissors.


  • Zulu time: Dada una hora en tiempo Zulu (Hora en el Meridiano de Greenwich) y la zona militar en la que el usuario desea saber la hora, el programa muestra la hora en esa zona. El formato para el dato de entrada es en formato de 24 horas ####, por ejemplo 2212 sería las 10:12 pm. Puedes encontrar la lista de zonas militares válidas en http://en.wikipedia.org/wiki/List_of_military_time_zones. Lo que sigue son ejemplos de cómo deben ser los resultados del programa:

    • Dada hora Zulu 1230 y zona A (UTC+1), el resultado debe ser 1330.
    • Dada hora Zulu 1230 y zona N (UTC-1), el resultado debe ser 1130.
    • Puerto Rico está en la zona militar Q (UTC-4), por lo tanto, cuando es 1800 en hora Zulu, son las 1400 en Puerto Rico.


      figure9.png

      Figura 9. Interfaz de la función Zulu time.


Instrucciones

  1. Para cada una de las funciones descritas arriba, escribe en tu libreta las pruebas que harás para determinar la validez de cada implementación (Alpha, Beta, Gamma y Delta). Para cada función, piensa en los errores lógicos que el programador pudo haber cometido y escribe pruebas que determinen si se cometió ese error. Para cada prueba, escribe los valores que utilizarás y el resultado que esperas obtener.

    Por ejemplo, puedes organizar tus respuestas en una tabla como la que sigue:


3 Sorts
Num Prueba Result Alpha Res. Beta Res. Gamma Res. Delta
1 "alce", "coyote", "zorro" "alce", "coyote", "zorro" .... ....
2 "alce", "zorro", "coyote" "zorro", "alce", "coyote" .... ....
.... .... .... .... .... ....

Figura 10. Tabla para organizar los resultados de las pruebas.


Puedes ver ejemplos de cómo organizar tus resultados aquí y aquí.

Ejercicio 2 - Hacer pruebas "a mano"

El proyecto testing implementa varias versiones de cada una de las cuatro funciones simples que se decribieron en el Ejercicio 1. Algunas o todas las implementaciones pueden estar incorrectas. Tu tarea es, usando las pruebas que diseñaste en el Ejercicio 1, probar las versiones de cada función para determinar cuáles de ellas, si alguna, están implementadas correctamente.

Este ejercicio NO requiere programación, debes hacer las pruebas sin mirar el código.

Instrucciones

  1. Carga a QtCreator el proyecto Testing. Hay dos maneras de hacer esto:

    • Utilizando la máquina virtual: Haz doble “click” en el archivo Testing.pro que se encuentra en el directorio /home/eip/labs/testing-testing de la máquina virtual.
    • Descargando la carpeta del proyecto de Bitbucket: Utiliza un terminal y escribe el comando git clone http:/bitbucket.org/eip-uprrp/testing-testing para descargar la carpeta tema-nombre de Bitbucket. En esa carpeta, haz doble “click” en el archivo Testing.pro.
  2. Configura el proyecto y corre el programa. Verás una pantalla similar a la siguiente:


    figure11.png

    Figura 11. Ventana para seleccionar la función que se va a probar.


  3. Selecciona el botón de 3 Sorts y obtendrás la interfaz de la Figura 5.

  4. La "Versión Alpha" en la caja indica que estás corriendo la primera versión del algoritmo 3 Sorts. Usa las pruebas que escribiste en el Ejercicio 1 para validar la "Version Alpha". Luego, haz lo mismo para las versiones Beta, Gamma y Delta. Escribe cuáles son las versiones correctas (si alguna) de la función y por qué. Recuerda que, para cada función, algunas o todas las implementaciones pueden estar incorrectas. Además, especifica cuáles pruebas te permitieron determinar las versiones que son incorrectas.

Ejercicio 3 - Usar assert para realizar pruebas unitarias

Hacer pruebas a mano cada vez que corres un programa es una tarea que puede resultar cansona bien rápido. En los ejercicios anteriores lo hiciste para unas pocas funciones simples. ¡Imagínate hacer lo mismo para un programa complejo como un navegador o un procesador de palabras!

Las pruebas unitarias ayudan a los programadores a validar códigos y simplificar el proceso de depuración ("debugging"), a la vez que evitan la tediosa tarea de hacer pruebas a mano en cada ejecución.

Instrucciones:

  1. En el menú de QtCreator, ve a Build y selecciona Clean Project "Testing". Luego ve a File y selecciona Close Project "Testing".

  2. Carga a QtCreator el proyecto UnitTests haciendo doble "click" en el archivo UnitTests.pro. Este archivo está incluido en la carpeta testing-testing.

  3. El proyecto solo contiene el archivo de código fuente main.cpp. Este archivo contiene cuatro funciones: fact, isALetter, isValidTime, y gcd, cuyos resultados son solo parcialmente correctos.

    Estudia la documentación de cada función (los comentarios que aparecen previo a cada función) para que comprendas la tarea que se espera que haga cada función.

    Tu tarea es escribir pruebas unitarias para cada una de las funciones para identificar los resultados erróneos. No necesitas reescribir las funciones para corregirlas.

    Para la función fact se provee la función test_fact() como función de prueba unitaria. Si invocas esta función desde main, compilas y corres el programa debes obtener un mensaje como el siguiente:

    Assertion failed: (fact(2) == 2), function test_fact, file ../UnitTests/ main.cpp, line 69.

    Esto es suficiente para saber que la función fact NO está correctamente implementada.

  4. Nota que, al fallar la prueba anterior, el programa no continuó su ejecución. Para poder probar el código que escribirás, comenta la invocación de test_fact() en main.

  5. Escribe una prueba unitaria llamada test_isALetter para la función isALetter. En la prueba unitaria escribe varias afirmaciones ("asserts") para probar algunos datos de entrada y sus valores esperados (mira la función test_fact para que te inspires). Invoca test_isALetter desde main y ejecuta tu programa. Si la función isALetter pasa las pruebas que escribiste, continúa escribiendo "asserts" y ejecutando tu programa hasta que alguno de los asserts falle.

  6. Comenta la invocación de test_isALetter en main para que puedas continuar con las otras funciones.

  7. Repite los pasos 5 y 6 paras las otras dos funciones, isValidTime y gcd. Recuerda que debes llamar a cada una de las funciones de prueba unitaria desde main para que corran.



Entregas

  1. Utiliza "Entrega 1" en Moodle para entregar la tabla con las pruebas que diseñaste en el Ejercicio 1 y que completaste en el Ejercicio 2 con los resultados de las pruebas de las funciones.

  2. Utiliza "Entrega 2" en Moodle para entregar el archivo main.cpp que contiene las funciones test_isALetter, test_isValidTime, test_gcd y sus invocaciones. Recuerda utilizar buenas prácticas de programación, incluye el nombre de los programadores, y documenta tu programa.



Referencias

[1] http://nifty.stanford.edu/2005/TestMe/




results matching ""

    No results matching ""