Laboratorio de Programación Documentación Pruebas Unitarias Dpto. de Ingeniería de Sistemas Telemáticos http://www.lab.dit.upm.es/~lprg/ febrero 2010 Documentación Documentación y Pruebas 2 1 ¿Qué es documentar? Documentar el código de un programa es añadir suficiente información como para explicar lo que hace, punto por punto, de forma que no sólo los ordenadores sepan qué hacer, sino que además los humanos entiendan qué están haciendo y por qué. No se trata sólo de rematar el trabajo, sino que responde a necesidades reales que pueden aparecer en el futuro: Extender el programa con nuevas funcionalidades Adaptarlo a un nuevo escenario Documentación y Pruebas 3 ¿Qué hay que documentar? Reglas básicas: Documentar/explicar aquello que no es evidente No repetir lo que se hace, sino explicar por qué se hace así Y esto se traduce en: ¿de qué se encarga una clase? ¿un paquete? ¿qué hace un método? ¿cuál es el uso esperado de un método? ¿para qué se usa una variable? ¿cuál es el uso esperado de una variable? ¿qué algoritmo estamos usando? ¿de dónde lo hemos sacado? ¿qué limitaciones tiene el algoritmo? ¿... la implementación? ¿qué se debería mejorar ... si hubiera tiempo? Documentación y Pruebas 4 2 Tipos de comentarios javadoc Delimitados por “/**” y “*/” Pueden abarcar varias líneas (que quizás comiencen por “*”) Permiten generar documentación externa al programa una línea Comienzan con “//” y terminan con la línea Para documentar código que no se desea que aparezca en documentación externa Pueden utilizarse varios seguidos tipo C Comienzan con “/*” y terminan con “*/” Pueden abarcar varias líneas Permiten “eliminar” código que no queremos olvidar del todo Documentación y Pruebas 5 ¿Cuándo documentar? Por obligación (javadoc) al principio de cada clase al principio de cada método antes de cada variable de clase Por conveniencia (una línea) al principio de un fragmento de código no evidente a lo largo de los bucles Por si acaso (una línea) siempre que se haga algo raro siempre que el código no sea evidente Documentación y Pruebas 6 3 Javadoc Es una herramienta del kit de desarrollo que permite generar documentación Web a partir del código Más que ayudar a comprender el código, se centra en la interfaz (API – Application Programming Interfaz) de las clases y paquetes Java Javadoc exige unos comentarios especiales: /** * Parte descriptiva * Que puede abarcar varias frases o párrafos * * @etiqueta texto específico para la etiqueta */ Documentación y Pruebas 7 Javadoc – Clases e Interfaces Al menos, deben usarse las etiquetas @author @version Etiquetas posibles @author Nombre del autor @version Identificació de versión y fecha @see Referencia a otras clases y métodos @since Indica desde qué versión o fecha existe la clase o interfaz en el paquete @deprecated Esta clase no debería usarse pues puede desaparecer en próximas versiones Documentación y Pruebas 8 4 Javadoc – Constructores y Métodos Al menos, deben usarse las etiquetas @param – una por argumento de entrada @return – si el método no es void @exception – una por tipo de Exception que pueda lanzar Etiquetas posibles @param Nombre del parámetro @return Descripción de su significado y uso Descripción de lo que se devuelve @exception Nombre de la excepción Excepciones que pueden lanzarse @since Indica desde qué versión o fecha existe este constructor o método en la clase @deprecated Este método no debería usarse pues puede desaparecer en próximas versiones Documentación y Pruebas 9 Javadoc – Atributos No hay ninguna etiqueta obligatoria Etiquetas posibles @since Indica desde qué versión o fecha existe este atributo en la clase @deprecated Este atributo no debería usarse pues puede desaparecer en próximas versiones Documentación y Pruebas 10 5 Ejecución de Javadoc <Directorio_Instalación>\javadoc Programa.java Múltiples opciones (entre ellas): usage: javadoc [options] [packagenames] [sourcefiles] [classnames] [@files] -public Show only public classes and members -protected Show protected/public classes and members (default) -package Show package/protected/public classes and members -private Show all classes and members -sourcepath <pathlist> Specify where to find source files -classpath <pathlist> Specify where to find user class files -verbose Output messages about what Javadoc is doing -d <directory> Destination directory for output files -version Include @version paragraphs -author Include @author paragraphs -docfilessubdirs Recursively copy doc-file subdirectories -splitindex Split index into one file per letter -windowtitle <text> Browser window title for the documenation -doctitle <html-code> Include title for the overview page -header <html-code> Include header text for each page -footer <html-code> Include footer text for each page -bottom <html-code> Include bottom text for each page Documentación y Pruebas 11 Referencias How to Write Doc Comments for the Javadoc Tool http://java.sun.com/j2se/javadoc/writingdoccomments/ Ejemplo de Javadoc http://java.sun.com/javase/6/docs/api/ Documentación y Pruebas 12 6 Pruebas Unitarias Documentación y Pruebas 13 Objetivo El objetivo único de las pruebas es encontrar errores en el código antes de que aparezcan en ejecución Una batería de pruebas es tanto mejor cuanto menos errores pasan desapercibidos Un programa es aceptable cuando: Hace lo que se acordó que debería hacer en las especificaciones No hace lo que no debe hacer Jamás debería entregarase un programa sin haberlo probado Documentación y Pruebas 14 7 Enfoque psicológico El que desarrolla comprueba que el programa funciona con todo lo que se le ocurre que debe funcionar El que prueba comprueba que el programa no falla con todo lo que se le ocurre que puede fallar Documentación y Pruebas 15 Tipos de prueba Pruebas de caja blanca: analizar el propio código (pruebas estructurales) Pruebas de caja negra: sin ver el código, probar la funcionalidad según especificaciones (pruebas funcionales) Pruebas de integración: probar cómo funciona el sistema completo, compuesto por módulos distintos. Pruebas de aceptación: realizadas el cliente para ver si se le entrega lo que pidió. Pruebas de regresión: tras añadir algo nuevo, para ver que no se ha descabalado la funcionalidad que ya había. Pruebas de robustez o solidez, de aguante, de prestaciones, etc. Documentación y Pruebas 16 8 Caja Negra: Casos de Prueba ¿Qué hay que probar? Todo lo que dice la especificación Manual Instrucciones Documentación adicional Hasta completar el 100% de cobertura Documentación y Pruebas 17 Caja Negra: Datos de Prueba Divida el espacio de pruebas en clases de equivalencia conjuntos de datos con comportamientos similares Elija un dato “normal” de una clase de equivalencia Pruebe con todos los datos “frontera” (valores extremos) Añada aquellos casos donde piense que el programador puede haberse equivocado Pruebe todas las combinaciones de {datos x comportamiento} Documentación y Pruebas 18 9 Caja Negra: Ejemplo Probar un método de ordenación de un array Input: 2 10 6 3 8 7 9 5 4 1 Output: 1 2 3 4 5 6 7 8 9 10 ¿Qué hay que probar? que todos los datos están en orden ascendente for (int i= 1; i < n; i++) assert (dato[i-1] <= dato[i]); ¿Con qué datos probar? Casos normales: N datos 10 datos al azar Casos singulares: 0 y 1 datos “sospechas” Elementos ya ordenados: 1 2 3 4 5 6 7 8 9 10 Elementos ordenados al revés: 10 9 8 7 6 5 4 3 2 1 Documentación y Pruebas 19 Caja Blanca: Casos de Prueba ¿Qué hay que probar? Ejecutar, al menos, una vez cada sentencia cobertura de sentencias Ejecutar al menos una vez cada condición con resultado cierto y falso cobertura de ramas Hasta completar el 100% del código if (...) Si T, si F switch (...) Cada ‘case’ + default Cobertura de bucles for -> 3 pruebas: 0 veces, 1 vez, n>1 veces repeat -> 2 pruebas: 1 vez, n>1 veces while-> 3 pruebas: 0 veces, 1 vez, n>1 veces Documentación y Pruebas 20 10 JUnit: Pruebas sistemáticas Prueba unitaria: una prueba individual de un método o clase. Prueba unitaria ad-hoc: por ejemplo, cuando creamos un objeto de cierta clase con BlueJ e invocamos manualmente un método del mismo con distintas entradas para ver si funciona. Sin embargo, con este tipo de pruebas no se puede trabajar eficiente y sistemáticamente. Cada vez que cambiamos algo en el método o clase tendríamos que volver a pasar todas las pruebas para asegurarnos de que “nada se ha descabalado”. Es decir, realizar pruebas de regresión. Para ello, vendría muy bien algo que nos permitiera definir sistemáticamente una serie de pruebas y ejecutarlas automáticamente, tantas veces como necesitáramos. JUNIT nos permite hacer esto (www.junit.org) Documentación y Pruebas 21 JUnit: Procedimiento Antes de implementar una determinada funcionalidad, piensa cómo deberías probarla para verificar que se comporta correctamente. Esto permite desarrollar la funcionalidad teniendo las ideas muy claras de lo que debería hacer. Escribe el código que implementa la funcionalidad deseada. Escribe el código de las pruebas inmediatamente después. Ejecuta las pruebas que hiciste. Corrige la unidad de código que implementa la funcionalidad deseada hasta que pase todas y cada una de las pruebas. Al añadir una nueva funcionalidad, repite el ciclo: piensa en cómo probarla, codifica la funcionalidad, codifica las pruebas, ejecuta todas las pruebas que hiciste (nuevas y viejas). No sigas hasta que el código pase absolutamente todas las pruebas. Así una y otra vez para cada nueva funcionalidad que implementes. Lo vemos con un ejemplo. Documentación y Pruebas 22 11 JUnit: Ejemplo (1) Desarrollar un método estático que tome un array de enteros como argumento y devuelva el mayor valor encontrado en el array. public class MayorNumero { /** * Devuelve el elemento de mayor valor de una lista * @param list Un array de enteros * @return El entero de mayor valor de la lista */ public static int mayorNumero(int lista[]) { return 0; // para que compile, hasta que //desarrollemos el método } } Documentación y Pruebas 23 JUnit: Ejemplo (2) ¿Qué pruebas pueden hacerse? Caso normal: array con valores cualesquiera [3, 7, 9, 8] -> 9 El mayor número se encuentra al principio o al final de la lista [9, 7, 8] -> 9 [8, 7, 9] -> 9 El mayor número está duplicado en el array [9, 7, 9, 8] -> 9 Sólo hay un elemento en el array [7] -> 7 Array compuesto por números negativos [-4, -6, -7, -22] -> -4 Documentación y Pruebas 24 12 JUnit: Ejemplo (3) Escribimos el código del método: /** * Devuelve el elemento de mayor valor de una lista * * @param list Un array de enteros * @return El entero de mayor valor de la lista */ public static int mayorNumero(int lista[]) { int indice, max = Integer.MAX_VALUE; for (indice = 0; indice < lista.length-1; indice++) { if (lista[indice] > max) { max = lista[indice]; } } return max; } Documentación y Pruebas 25 JUnit: Ejemplo (4) Escribimos el código de las pruebas: import junit.framework.*; public class TestMayorNumero extends TestCase { public TestMayorNumero() { } public void testSimple() { assertEquals(9, MayorNumero.mayorNumero(new int[] {3, 7, 9, 8})); } public void testOrden() { assertEquals(9, MayorNumero.mayorNumero(new int[] {9, 7, 8})); assertEquals(9, MayorNumero.mayorNumero(new int[] {7, 9, 8})); assertEquals(9, MayorNumero.mayorNumero(new int[] {7, 8, 9})); } public void testDuplicados() { assertEquals(9, MayorNumero.mayorNumero(new int[] {9, 7, 9, 8})); } public void testSoloUno() { assertEquals(7, MayorNumero.mayorNumero(new int[] {7})); } public void testTodosNegativos() { assertEquals(-4, MayorNumero.mayorNumero(new int[] {-4, -6, -7, 22})); } public static void main (String args[]) { junit.textui.TestRunner.run(TestMayorNumero.class); } } Documentación y Pruebas 26 13 JUnit: Ejemplo (4) Fallos detectados: Error de concepto: MAX_VALUE por MIN_VALUE Valor frontera en un bucle Error en la propia definición de las pruebas Documentación y Pruebas 27 JUnit Marco para desarrollar pruebas unitarias Pasos: Importar las clases de JUNIT necesarias Definir la clase de pruebas: Debe extender la clase “TestCase” Definir los métodos de prueba Deben comenzar por “test” Serán ejecutados automáticamente por JUNIT Definir un main o ejecutar desde un IDE junit.textui.TestRunner.run(<clase>) Documentación y Pruebas 28 14 Junit: comprobaciones assertEquals (valor_esperado, valor_real); Los valores pueden ser de cualquier tipo Si son arrays, no se comprueban elemento a elemento, sólo la referencia assertTrue (condición_booleana) assertFalse (condición_booleana) assertSame (Objeto esperado, Objeto real) Comprueba que son la misma referencia assertNotSame (Objeto esperato, Objeto obtenido) Comprueba que son referencias distintas assertNull (Objeto) Comprueba que el objeto es Null assertNotNull (Objeto objeto) Comprueba que el objeto no es Null fail (string Mensaje) Imprime el mensaje y falla Útil para comprobar que se capturan excepciones Documentación y Pruebas 29 Junit: Uso de las comprobaciones En una función de prueba (“testXXX”), se pueden poner tantos métodos de comprobación como sean necesarios para implementar el caso de prueba concreto. A la hora de ejecutar la función prueba (“testXXX”), en cuanto falle uno de los métodos de comprobación se para la ejecución. No se ejecutan el resto de métodos de comprobación tras el que falló. En ese caso, antes de seguir es MUY aconsejable corregir el fallo que se ha producido. En general hay que comprobar que un método lanza todas las excepciones que se han declarado en el mismo cuando debe. Y que no las lanza cuando no hay motivo para ello. Esta es la utilidad del método fail. public void testExcepcionOrdenarListaNula( ) { try { ordena_lista(null); fail(“Debería haber lanzado una excepción”); } catch (RuntimeException e) { } } Documentación y Pruebas 30 15 Junit: setUp & tearDown Se pueden poner métodos para envolver las pruebas: public void setUp() { ...; } public void tearDown() { ...; } setUp() setUp() setUp() test001() test002() test003() tearDown() tearDown() tearDown() Documentación y Pruebas 31 16