Prueba de programas Projecte de Programació (PROP) Prueba de programas • Proceso de ejecución y evaluación, manual o automática, de un sistema informático, con los siguientes objetivos: – Eliminación de los errores de especificación, diseño e implementación – Verificación de los requisitos funcionales (qué funcionalidad falta? es lo que el usuario quería?) – Verificación de los requisitos no funcionales • Coste: hasta el 50% del tiempo! 2 Prueba de programas Configuración Software a probar Correcciones Resultados Evaluación Errores Software Corregido Depuración Prueba Tasa error Configuración Prueba (Juegos Pruebas) Modelo De Fiabilidad Predicción Fiabilidad 3 Prueba de programas • Detección vs. Corrección (+ localización) • Detección -> Métodos: – Verificación formal (proving) – Pruebas (testing) “Program testing can be used to show the presence of bugs, but never to show their absence” – Dijkstra "Be careful about using the following code -- I've only proven that it works, I haven't tested it." – Knuth “Test your software or your users will” - Hunt & Thomas 4 Verificación formal • Demostración formal de que el programa satisface la especificación Obstáculos: • Técnicas complejas. Personal cualificado • No garantiza la ausencia de errores de especificación • Raramente se dispone de una especificación formal del resto de componentes del sistema (p.ej., librerías, entrada/salida, …) • Uso en fragmentos críticos del programa y/o en paralelo con el diseño 5 Pruebas Estado entrada Programa (transformador de estados) Ideal: Batería de ejecuciones para probar todos los estados de entrada Impracticable! Testing: Escoger un Estado salida subconjunto representativo de todos los estados 6 Pruebas (2) • Técnica más barata y más usada – habilidad programador – sistemática • No garantiza la ausencia de errores de – especificación – diseño – implementación Seguimiento manual Prueba sistemática • Se puede hacer más o menos exhaustiva, según la importancia asignada a la fiabilidad 7 Pruebas (3) Nombres: • Test case (una prueba) • Test suite (un juego de pruebas) • Test plan (una colección de juegos de pruebas) • Scenario test (una tarea que querría realizar un usuario hipotético) Tarea: Diseñar los anteriores para cubrir todas (???) las posibilidades de ejecución Entradas: Ficheros, BDs, valores parámetros/ctes., valores a introducir interactivamente, … Predefinir también las salidas! 8 Prueba de programas Niveles: • Pruebas de componentes • Pruebas de integración • Pruebas de sistema • Test de aceptación 9 Pruebas de componentes En O.O.: componente Técnicas: • De caja blanca clase – Estructura interna • De caja negra – funcionalidad 10 Pruebas de componentes: Caja blanca • Necesario el acceso al código del componente • Batería para provocar la ejecución, al menos una vez, de cada instrucción del programa Opciones: cada rama del “if” se ha de ejecutar Bucles: ejecutar casos límite (ejecutar el bucle o no) e interior Estructuras de datos: internamente (prueba completa según su casuística) • Más general: cada posible camino. • Más general: cada camino con diferentes valores 11 Pruebas de componentes: Caja blanca (2) { s1; while (c1) { if (c2) s2 else s3; s4; } if (c3) s5 else s6; } s1 inici c3 c1 s5 s6 c2 s2 s3 s4 final Método basis set : 1) Escoger un conjunto de caminos que cubren todos los arcos 2) Poner un test case para cada camino 12 Pruebas de componentes: Caja blanca (3) Método dataflow: • Localizar qué puntos en el programa dan valor a una variable y cuáles la consumen • Para cada variable x definir conjuntos de instrucciones Def(x) (donde se asigna) y Use(x) (donde se lee) • Para cada instrucción s, definir Def(s) y Use(s) a partir de los Def(x) y Use(x) • Definir cadenas DU (Def-Use): (x,s1,s2) si x está en Def(s1) y en Use(s2) y la definición de x en s1 aún es válida en s2 • Necesario cubrir al menos una vez cada cadena DU 13 Pruebas de componentes: Caja negra • Complementario y normalmente posterior a caja blanca • Se usa sólo la interficie del componente • Tipos error: – – – – – funcionalidades incorrectas o no previstas errores de interficie errores de E.D. externas errores de funcionamiento errores de inicialización o terminación • Juegos de prueba mínimamente completos: – Dividir el dominio de cada variable en “clases de equivalencia” (comportamiento uniforme - subjetivo…) Ej: x Є N {0} {x | 0 < x < 7} {x | x ≥ 7} – Escoger un representante de cada clase – Test suite: todas las combinaciones de representantes (conjunto cociente) • Atención a los casos extremos / valores frontera 14 Concurrencia / tiempo real Necesario comprobar, además: • Secuenciación temporal (timing) de los datos • Paralelismo / interacción entre procesos • Problemas de bloqueo y competencia entre procesos/recursos • Reacción ante eventos externos/ relación con el entorno de ejecución • Reparto de carga • Recuperación de errores (hardware, otros programas, ...) • Sistemas operativos, sistemas de fabricación, redes, … • Código de control de errores llega a ser el 70% 15 Estrategia de prueba Etapas: • Planificación prueba: orden de prueba de las piezas • Diseño: casos a probar y resultados esperados • Prueba: ejecución • Obtención de resultados • Evaluación 16 Pruebas de integración • Se prueban interacciones entre componentes A nivel de test: comp. no probado <-> comp. no construido • Integración incremental: No “Big Bang” • También “caja blanca + caja negra” • Enfoques: – Ascendente: Drivers Programa que prueba un componente de “nivel más bajo” – Descendente: Stubs Programa que permite probar otro “de nivel más alto” (Los niveles vienen marcados por los distintos tipos de relaciones entre componentes utilizadas) 17 Pruebas de integración: Drivers Presentar resultados Leer datos de test Driver Retornar resultados Invocar métodos Clase a probar Atención: Se piden drivers interactivos!! Que no haga falta recompilar para probar con nuevos datos! Entrada por teclado o ficheros de texto 18 plano! Pruebas de integración: Stubs Stub: representa una clase C con interficie conocida pero aún no ejecutable Clase a probar Retornar resultados Invocar métodos Stub Implementa una versión simple de la funcionalidad final de C P.ej. • Escribir “se ha llamado el método m de C con parámetros x” • Retornar datos constantes • ... 19 Caso particular: Clases abstractas C: Clase abstracta Stub concreto El stub concreto implementa los métodos abstractos de C de manera simple. Permite crear instancias de C para probar los métodos implementados en C (eventualmente por medio de otros stubs y drivers) 20 Pruebas de integración • Enfoques: – Ascendente: Drivers -) El programa no existe hasta el final +) Los componentes de nivel más bajo pasan más pruebas – Descendente (profundidad/anchura): Stubs -) Construcción de stubs +) Ya tenemos el programa entero – solapamiento con pruebas de validación – Métodos híbridos: Sandwich Testing Si conviene comenzar probando los componentes más críticos: • Reutilización • Visibilidad del producto • Grado de acoplamiento y uso de los distintos componentes • Adquisición de datos a partir del usuario 21 Pruebas de integración • Stubs, drivers y test suites son el soporte (scaffolding) para construir nuestro sistema • Serán retirados al final • Pero pasar tiempo diseñándolos NO es tiempo perdido • Escribe mucho más código que el que irá en la versión final 22 Pruebas Consejos: • Prueba pronto: ahorrarás tiempo y € • Escribe tests que prueben propiedades bien definidas • Escribe tests que se entiendan • Documenta los tests (formulario!) • Uso de herramientas: JUnit • Escribe los tests antes que el código • Escribe mucho código para hacer testing 23 Descripció dels jocs de proves Cada fitxer executable del segon lliurament ha d’anar acompanyat d’un text que descrigui: •Objecte de la prova: casos d'ús i classes, o integració de conjunts, provats anteriorment, que es proven •Altres elements integrats a la prova: classes ja provades o que s'han provat d'alguna altra manera –reutilitzats, etc.– integrades en aquest executable (si n'hi ha) •Drivers construïts per aquesta prova i integrats en l'executable, amb descripció de la seva missió (si n'hi ha) •Stubs construïts per aquesta prova i integrats en l'executable, amb descripció de la seva missió (si n'hi ha) •Fitxers de dades necessaris: nom dels fitxers de dades, si n'hi ha, i tal cas les classes que s'emmagatzemen en cadascun •Valors estudiats: amb quins valors es prova i quina missió tenen, aquí és on s'hauria de parlar de caixa blanca o negra, conjunts de dades de comportament homogeni, etc. •Efectes estudiats: funcionalitats com ara vistes, moviments per la pantalla, selecció en llistes, etc. que no són dades concretes •Operativa: petit manual o descripció del funcionament de l'executable, molt bàsica 24 Regresión • Partes que funcionaban dejan de funcionar – Cuando añadimos funcionalidad – Cuando rediseñamos (“mejoramos”) una parte • Bugs supuestamente eliminados reaparecen (regression bugs) • Especialmente, en tareas de mantenimiento 25 Regresión (2) • Test de regresión: Ejecutar todas las baterías ya superadas en etapas anteriores cuando introducimos cambios • … en cada “build”, o cada noche/semana • Importante: añadir test cases para cada bug ya eliminado! • Herramientas semi-automáticas para testing (Junit, …) 26 Junit (1) • Conjunto de bibliotecas creadas por Erich Gamma y Kent Beck que son utilizadas en programación para hacer pruebas unitarias de aplicaciones Java. (wikipedia) • JUnit es un conjunto de clases (framework) que permite realizar la ejecución de clases Java de manera controlada, para poder evaluar si el funcionamiento de cada uno de los métodos de la clase se comporta como se espera. Es decir, en función de algún valor de entrada se evalúa el valor de retorno esperado; si la clase cumple con la especificación, entonces JUnit devolverá que el método de la clase pasó exitosamente la prueba; en caso de que el valor esperado sea diferente al que regresó el método durante la ejecución, JUnit devolverá un fallo en el método correspondiente. 27 JUnit (2) • JUnit es también un medio de controlar las pruebas de regresión, necesarias cuando una parte del código ha sido modificado y se desea ver que el nuevo código cumple con los requerimientos anteriores y que no se ha alterado su funcionalidad después de la nueva modificación. • El propio framework incluye formas de ver los resultados (runners) que pueden ser en modo texto, gráfico (AWT o Swing) o como tarea en Ant. • En la actualidad las herramientas de desarrollo como NetBeans y Eclipse cuentan con plug-ins que permiten que la generación de las plantillas necesarias para la creación de las pruebas de una clase Java se realice de manera automática, facilitando al programador enfocarse en la prueba y el resultado esperado, y dejando a la herramienta la creación de las clases que permiten coordinar las pruebas. 28 Pruebas de validación/ de sistema Instrumentos • Producto completo • Especificación de requerimientos funcionales y operativos • Documentación de usuario Pruebas de sistema (entorno real): • Seguridad • Recuperación (caída de sistema) • Potencia (condiciones extremas: volumen, frecuencia, horas punta, número de usuarios) • Eficiencia (tiempo de respuesta, consumo recursos, …) • Interacción con otro software • Usabilidad… 29 Test de aceptación • Realizado por el cliente/usuario final • Pruebas de aceptación controladas: – alfa: con presencia del programador – beta: sin presencia del programador Hasta cuándo probar? • Hasta que se acaba el tiempo o el dinero! • Cuando #errores detectados/unidad de tiempo tiende a 0 • Producto final = Versión pre-alfa del siguiente producto 30 Depuración de errores (debugging) Etapas para cada error: • Detección (síntomas) • Localización (causas) • Determinación de la fase en que se ha producido (especificación-diseño-implementación) • Evaluación de la gravedad (leve-grave-crítico) y del coste de corrección • Corrección • Comprobación (-> tests de regresión) 31 Depuración de errores (debugging) (2) Algunas heurísticas: • Brute Force Approach: Seguimiento sistemático del funcionamiento del programa • Backtracking: Retroceder desde el punto donde se ha detectado el síntoma. Problema: efecto lateral? • Localizar puntos del programa donde se pudieron producir las causas del síntoma: Mirar variables que intervienen y asignaciones de éstas • Comprobación de los contratos de las llamadas (pre y postcondiciones) • Assert de Java / librería InOut de PRAP • Regresión: arreglar un bug ha generado otros? 32 Java – Assert statement assert Expresión_booleana assert Expresión_booleana: Expresion_textual AssertionError [“ “+Expresion_textual] • Las aserciones se usan para afirmar o comprobar suposiciones sobre el estado del programa que asumimos ciertas: situaciones imposibles, precondiciones, invariantes, postcondiciones,... • Su checking se puede habilitar/deshabilitar (de forma selectiva o no para clases/packages). • En principio se usan durante el desarrollo y se deshabilitan durante la explotación • OJO con efectos laterales de Expresion_booleana 33 Experiencia Consejo: • Un error ha de servir para evitar repetirlo • Reflexionad: por qué he cometido este error? • Compartidlo con el resto del grupo! “Experience is the name everyone gives to his mistakes” - Oscar Wilde, Lady Windemere's Fan “Human beings, who are almost unique in having the ability to learn from the experience of others, are also remarkable for their apparent disinclination to do so” Douglas Adams, Last Chance to See “Experience is what you get when you don’t get what you want” - Dan Stanford 34 Excepciones Public boolean leeDatos (File fichero) throws ExcepFichero1, IOException,… {… if (linea==null) throw new ExcepFichero1(fichero.getName()); …} Public class ExcepFichero1 extends Exception { public string filename; public ExcepFichero1 (string nombre) { super(“Error en fichero” + nombre + “, formato incorrecto”); filename= nombre; } } Al invocar un método con excepciones en su “throws” puedes: • Capturar (Catch) la excepción y gestionarla • Capturar la excepción y mapearla en una de tus excepciones • Declarar la excepción en tu “throws” y pasarla (pila de llamadas)35 Excepciones • Gestionan condiciones inusuales/inesperadas durante la ejecución: – Problemas de estado interno de un objeto (valores de variables inconsistentes, …) – Errores en manipulación de objetos o datos (ficheros, direcciones de red, …) – Violación de contrato básico del objeto (leer datos de una stream ya cerrada, …) • Forma limpia de chequear errores sin llenar el flujo de código con chequeos. • Hacen que las condiciones de error que un método puede señalar sean parte explícita de la cabecera de ese método (su contrato). • La lista de excepciones puede ser: – vista por el programador – chequeada por el compilador – preservada por las clases que heredan y redefinen el método (compatible: lanzar las mismas excepciones o menos, nunca más, ni nuevos tipos, ni más generales que en la superclase) 36