1 junit y eclemma
Post on 29-Dec-2015
118 Views
Preview:
TRANSCRIPT
Politécnico Grancolombiano
10
Herramientas de
Desarrollo JUnit y EclEmma
Diego A. Cabra
Herramientas de desarrollo
JUNIT Y ECLEMMA
La prueba puede ser definida como:
«Una actividad en la cual un sistema o componente es ejecutado bajo
condiciones específicas, se observan o almacenan los resultados y se realiza
una evaluación de algún aspecto del sistema o componente.» [IEE E90]
Durante su desarrollo, el software atraviesa diferentes etapas y estados que hacen
parte de un proceso evolutivo que comúnmente se conoce como ciclo de vida del
software. Aunque para algunos desarrolladores, la primera y última etapa consiste
en la implementación del sistema, el trabajo de un desarrollador no termina en el
momento que entrega el producto. Es claro que el producto es susceptible a fallos
y que no es muy probable que se detecten durante el proceso de implementación,
por tanto la posibilidad de que el desarrollador deba retomar su trabajo luego de la
entrega final es bastante alta. Lo delicado aquí es en qué momento surgirán estos
fallos, y cuál será su costo de reparación. No sería un buen momento para
percatarse de que el sistema de trazado de rutas de vuelos comerciales tiene un
peligroso desfase cuando hay miles de aviones recorriendo las rutas que ese
sistema diseñó. Es por esto que dentro del ciclo de vida del software debe
contemplarse una etapa dedicada a probar la aplicación en busca de estos fallos
para poderlos corregir con el menor costo posible. La etapa de pruebas es
fundamental para asegurar la calidad y confiabilidad del sistema.
El testing es el proceso de encontrar diferencias entre el comportamiento
esperado (requerido) y el comportamiento observado (existente) en los sistemas
implementados. El testing intenta mostrar que la implementación del sistema es
inconsistente con el modelo de sistema especificado. La aplicación de pruebas
tiene como objetivo identificar posibles fallos, errores, o defectos en la
implementación, diseño, calidad y/o usabilidad de un proyecto de software. La
diferenciación entre fallos, errores y defectos no es fortuita y cabe distinguir a que
se refiere cada uno de estos conceptos
Los fallos son cualquier diferencia entre el comportamiento observado y el comportamiento esperado.
Un error representa que el sistema está en un estado que más adelante provocará un fallo del sistema, lo que implica que el sistema se desvía del comportamiento esperado.
Un defecto es la causa mecánica o algorítmica de un estado erróneo. Con las pruebas se busca verificar, analizar y evaluar la calidad de un producto de
software. Las pruebas se elaboran orientadas a quebrantar el sistema objetivo y a
maximizar la cantidad de defectos identificados, no buscan comprobar que el
sistema no tiene fallas, sino permitir a los desarrolladores corregir los defectos a
tiempo, para incrementar la confiabilidad del sistema. Una prueba exitosa es
aquella que encuentra faltas o defectos.
La confiabilidad es una medida de éxito que asegura que el comportamiento
observado de un sistema se ajusta a la especificación de su comportamiento.
Encontrar a tiempo posibles fallas o errores es crucial, pues permite minimizar el
costo en tiempo y dinero de la reparación de tales fallas y permite cumplir con los
objetivos del proyecto. Se estima que aproximadamente la mitad del esfuerzo en
el desarrollo de un programa (tiempo y costos) se invierte en la fase de pruebas.
Por tal motivo, las pruebas deben planificarse con suficiente anticipación aplicando
diversas metodologías y estrategias.
Idealmente, se procuraría exponer el programa o el software a todas las
situaciones posibles con el fin de encontrar todos los potenciales fallos que este
pueda tener, pero tal intención es prácticamente imposible de llevar a cabo desde
el punto de vista humano, económico y de tiempo. Esto invoca que la perfección
en un programa quizás no pueda ser alcanzada ni garantizada, por lo que el
proceso de pruebas se convierte en un proceso indispensable, de sumo cuidado y
responsabilidad en procura de maximizar la cantidad de defectos encontrados que
finalmente conlleven a fallos, esto por medio de la aplicación de estándares y
metodologías que permitan que el consumo de recursos destinados a esta
actividad sean aceptables.
No es recomendable que las pruebas sean realizadas por el mismo equipo de
desarrollo. Para ser más eficaces las pruebas deben ser realizadas por un equipo
independiente al equipo de desarrollo, que puede concebir una visión más objetiva
del software.
El proceso de pruebas comienza en la primera fase de desarrollo, con la
elaboración de un plan de pruebas donde se asignan los recursos y los horarios
de las pruebas; a partir de este plan, durante la fase de implementación, se detalla
el diseño de las pruebas como tal, se prepara y se configura el software sobre el
que se van a ejecutar los casos de prueba y seguidamente, ya en la etapa de
pruebas, se ejecutan las pruebas especificadas. Al finalizar el proceso, se hace
una comparación entre la salida esperada y los resultados generados por las
pruebas, lo que resulta en información importante para la toma de medidas
adecuadas con el fin de minimizar o corregir los fallos.
Un caso de prueba es un conjunto de entradas y resultados esperados que se
aplica a un componente con el propósito de causar fallos y detectar defectos.
Un componente es una parte del sistema que puede ser aislada para hacer
pruebas (objeto, grupo de objetos, uno o más subsistemas).
Diseñar un caso de prueba consiste en elegir aquellas pruebas que por sus
características particulares se diferencian del resto. De esta forma se asume, que
si al ejecutar estos casos de prueba no se presentan errores o fallos se puede
tener una cierta garantía de que el programa no tiene defectos.
Las pruebas deben empezar por lo pequeño y progresar a lo grande, es por esto
que existen diferentes niveles de pruebas:
Pruebas Unitarias
Pruebas de integración
Pruebas del sistema
Pruebas funcionales
Pruebas de desempeño
Pruebas piloto
Pruebas de aceptación
Pruebas de instalación
Pruebas Unitarias: Son pruebas que se aplican sobre un único componente. Las
pruebas unitarias sirven para asegurarse que cada unidad de código o
componente funciona correctamente por separado, minimizando de esta forma la
complejidad de las pruebas generales. Las pruebas unitarias verifican la estructura
de cada componente y permite el paralelismo en las actividades de pruebas ya
que cada componente puede ser testeado o probado independientemente de los
otros.
Pruebas de integración: Estas pruebas detectan los defectos que no fueron
identificados durante las pruebas unitarias. Estas pruebas consisten en probar o
testear dos o más componentes integrados. Si el resultado de estas pruebas no
revela defectos se integran más componentes al grupo. Estas pruebas consisten
en verificar que un gran conjunto de porciones de código funcionan correctamente
juntos.
Pruebas del sistema: Una vez los componentes han sido integrados y probados,
las pruebas de sistema aseguran que el sistema completo cumple con los
requerimientos funcionales y no funcionales. Las pruebas del sistema, prueban
todos los componentes juntos, vistos como un solo sistema para identificar
defectos. Busca encontrar diferencias entre el programa desarrollado y sus
requerimientos funcionales y no funcionales.
Dentro de las pruebas del sistema se realizan cinco tipos de pruebas diferentes,
estas son:
Pruebas funcionales: Las pruebas funcionales buscan encontrar las
diferencias entre los requerimientos funcionales y el desempeño del sistema. Para
esta actividad los casos de prueba definidos son derivados de los casos de uso. El
objetivo es seleccionar aquellas pruebas que son representativas para el usuario y
que tienen alta probabilidad de evidenciar un fallo.
Pruebas de desempeño: Las pruebas de desempeño buscan encontrar las
diferencias entre los requerimientos no funcionales especificados en el diseño y el
desempeño del sistema. En esta actividad se realizan pruebas de seguridad, de
sincronización, de recuperación, de capacidad de almacenamiento y de respuesta
a solicitudes.
Pruebas piloto: Las pruebas piloto consisten en instalar el sistema en
equipos que simulan las características del entorno real en donde será usado
finalmente el software, una vez instalado, el software es usado por un conjunto de
usuarios seleccionados; lo que permite que estos usuarios identifiquen fallos o
defectos antes de liberar el software.
Pruebas de aceptación: El objetivo de estas pruebas es validar que el
sistema cumple con el funcionamiento deseado o esperado. Estas pruebas son
definidas por el cliente o el usuario del sistema y representan las condiciones
típicas bajo las cuales el sistema debe operar. Estas pruebas le permiten al
usuario o cliente aceptar el sistema desde el punto de vista funcional y de
rendimiento.
Pruebas de instalación: Se comprueba el correcto funcionamiento del
sistema y el cumplimiento de los requerimientos no funcionales especificados,
instalado en su entorno real. Se revisan que los manuales de instalación
correspondan a la correcta instalación del sistema. Generalmente, durante las
pruebas de instalación se repiten los casos de prueba ejecutados durante las
pruebas funcionales y de desempeño pero en el ambiente real.
Para llevar a cabo el proceso de pruebas, es necesario contar con profesionales
altamente calificados y capacitados. Estos profesionales deben saber elegir los
casos de prueba adecuados, deben conocer el lenguaje de desarrollo y el manejo
de herramientas especializadas, además de conocer y aplicar efectiva y
eficientemente metodologías y técnicas de pruebas. El equipo de pruebas del
sistema debe contar con un detallado entendimiento de todo el sistema, desde los
requerimientos, el diseño y la implementación.
Actualmente existen diversas herramientas de pruebas que efectúan y apoyan el
proceso de pruebas de software. Encontramos herramientas usadas para el
seguimiento de defectos, herramientas de carga y rendimiento, herramientas de
gestión y manejo de pruebas, herramientas para pruebas de unidad y de
cubrimiento de código, y herramientas especializadas. En esta unidad
estudiaremos específicamente dos herramientas, JUnit que lleva a cabo el
desarrollo de pruebas unitarias y Emma que lleva a cabo pruebas de cobertura de
código.
JUNIT: HERRAMIENTA PARA PRUEBAS UNITARIAS
Como se mencionó anteriormente, el proceso de pruebas puede llegar a ser un
proceso costoso en tiempo y recursos, por lo que es recomendable diseñar y
ejecutar pruebas unitarias que minimicen la complejidad de las pruebas generales.
Una buena práctica para llevar a cabo estas pruebas unitarias es que luego de
implementar un método, clase o componente (según las necesidades y la
complejidad del proyecto), se lleve a cabo inmediatamente el caso o los casos de
prueba necesarios para comprobar y asegurar que el comportamiento, de lo que
se ha acabado de implementar, coincida con el comportamiento esperado. Por
ejemplo, se elaboró un método que suma dos enteros y devuelve la suma de los
enteros al cuadrado. En lugar de comprobar que el método funciona
correctamente por medio de mensajes en la consola (System.out.println()) que no
es una buena práctica, se desarrolla un caso de prueba que permite comprobar el
funcionamiento del método sin afectar o alterar el código del mismo.
Entre las herramientas para pruebas unitarias encontramos una muy potente
llamada JUnit, en la cual concentraremos nuestra atención, pues esta herramienta
automatiza la ejecución de este tipo de pruebas en programas escritos en Java.
JUnit es una herramienta de pruebas gratuita creada por Kent Beck y Erich
Gamma quienes suman una amplia experiencia en el trabajo con patrones de
diseño y la metodología de desarrollo de software extreme programming (XP).
JUnit es un conjunto de bibliotecas que permiten ejecutar de forma controlada una
clase java, con el fin de evaluar el funcionamiento sus métodos. Esta herramienta
es muy popular entre los desarrolladores de software, debido a su facilidad de uso.
Con JUnit se pueden desarrollar casos de prueba (Test Case) y colecciones de
pruebas (Suite Test). JUnit ofrece una interfaz gráfica que permite visualizar y
ejecutar las pruebas, verificar los resultados, entre otras acciones.
En JUnit los casos de prueba son clases que disponen de métodos para probar
los métodos de una clase o un modulo completo. Para cada clase que se quiera
probar por medio de JUnit es necesario definir una clase de caso de prueba e
implementar los métodos de prueba deseados. Una Suite test es una clase java
que ejecuta uno o varios casos de prueba.
La última versión de JUnit (v.4) simplifica aun más el trabajo, en comparación con
la versión anterior (v.3), ya que aprovecha las anotaciones, una característica de
Java disponible desde la versión 1.5 del JDK. Las anotaciones son observaciones
que se pueden añadir a una clase, un método, un atributo etc. para describirlos
mejor. Una anotación son una especie de marcas o etiquetas, que agregan
metadatos al código fuente; por medio de estos metadatos se le indica a la JVM
como interactuar con los elementos del programa.
En el caso de JUnit v.4, se eliminaron las convenciones de nombrado para ubicar
los métodos de prueba. En cambio ahora, para señalar que un método es un
método de prueba se coloca antes de la declaración del método la anotación
@Test, esto también elimina la necesidad de heredar la clase de prueba de la
clase TestCase y de colocar el prefijo test a cada uno de los métodos de prueba,
que en las versiones anteriores era totalmente necesario.
JUnit cuenta con varios métodos que permiten hacer las comprobaciones
necesarias a los métodos de la clase que está siendo probada. Estos métodos
comparan el comportamiento observado u obtenido al ejecutar el caso, con el
comportamiento esperado.
Ejemplos de estos métodos son:
Métodos assertXXX() Que comprueba
assertTrue(expresión) Este método comprueba que al evaluarse la expresión esta devuelva el valor booleano true. Devuelve assertionError si no se produce el resultado esperado
assertFalse(expresión) Este método comprueba que al evaluarse la expresión esta devuelva el valor booleano false.
assertEquals(comportamiento esperado, comportamiento actual)
Comprueba que el comportamiento esperado sea igual al comportamiento actual o real. Devuelve assertionError si no se produce el resultado esperado.
assertNull(Objeto) Comprueba que la referencia al Objeto sea nula (“null”).
assertNotNull(Objeto) Comprueba que la referencia al Objeto no sea nula (“null”).
assertSame(Objeto esperado, Objeto real)
Comprueba que el objeto esperado y el objeto real sean el mismo objeto.
assertNotSame(Objeto esperado, Objeto real)
Comprueba que el objeto esperado y el objeto real no sean el mismo objeto.
Fail() Devuelve una alerta informando el fallo del test.
assertArrayEquals Recibe como parámetro 2 arreglos y comprueba si son iguales. Devuelve assertionError si no se produce el resultado esperado.
La lista completa de todos los métodos que se pueden usar para hacer pruebas, la
pueden consultar en el siguiente link del API de JUnit:
http://junit.org/apidocs/org/junit/Assert.html
Además de la anotación @Test, existen otras anotaciones que se colocan antes
de los métodos, las principales son:
@BeforeClass: El método que tenga antes de su implementación esta anotación
será invocado al principio, es decir antes del lanzamiento de todas las pruebas.
Este método suele ser utilizado para inicializar atributos comunes para todas las
pruebas o también para desempeñar una actividad que generalmente consume
mucho tiempo, por ejemplo la conexión a una base de datos. En una clase de
prueba solo puede existir un único método con esta anotación y solo puede ser
invocado una sola vez.
@AfterClass: Este método será invocado una sola vez, cuando finaliza la
ejecución de todas las pruebas. Al igual que el anterior, solo puede haber un
método asociado con esta anotación. Este método suele ser utilizado para liberar
recursos, por ejemplo la desconexión a una base de datos.
@Before: En un método con esta anotación se permite reunir instrucciones de
inicialización de objetos comunes para todos los métodos prueba. Comúnmente,
se usa para configurar o preparar el ambiente de prueba para los métodos. Esto
es importante, ya que suele suceder que los métodos de prueba usan un mismo
objeto en común modificando sus atributos o estado, para efectuar las respectivas
comprobaciones; si el siguiente método de prueba toma este mismo objeto con
tales modificaciones, puede provocar que los resultados que arroja su ejecución
no sean los esperados.
@After: Un método con esta anotación reúne las instrucciones necesarias liberar
los recursos de cada test, por ejemplo destruir referencias de objetos. Los
métodos con esta anotación serán invocados después de terminar cada prueba.
@Ignore: Los métodos que son marcados con esta anotación no serán
ejecutados. Esta anotación suele colocarse cuando un método de prueba no
puede o no debe ser ejecutado por algún motivo.
De manera más concreta, la anotación @Test puede tener dos tipos de
modificadores:
@Test (timeout = X): significa que la prueba será válida si se ejecuta en un tiempo
máximo de X milisegundos.
@Test (expected = java.util.NoSuchElementException.class): significa que la
prueba será válida solo si se lanza la excepción esperada.
Pruebas de cobertura
Antes de finalizar, es importante mencionar que existen también otras
herramientas desarrolladas bajo la tecnología java que pueden ser integradas a
Eclipse y que pueden proveer información importante acerca de la ejecución de
pruebas.
Las pruebas unitarias como las que se trabajan con JUnit se denominan pruebas
de caja negra, pues al ejecutar la prueba, la implementación del modulo no tiene
importancia, es decir no importa como hace las cosas, el caso es que las haga
bien; ahora si las hace mal, pues habrá que hacer cambios en la implementación,
pero esto está fuera del alcance de la prueba. Otro estilo de pruebas son las
denominadas de caja blanca o caja de cristal, las cuales se centran en los detalles
procedimentales de los módulos, no solo importa lo que hace el modulo, sino
también como hace las cosas. Estas pruebas están muy ligadas al código fuente,
ya que para un caso de prueba no solo se analiza el resultado, sino el flujo de
ejecución del programa. Se pueden aplicar en diferentes niveles (unidad,
integración y sistema) pero es habitual que se apliquen únicamente como pruebas
unitarias.
Un tipo de prueba de caja blanca aplicable al nivel de unidad, son las pruebas de
cobertura, las cuales indican que porcentaje del código fuente de un modulo se ha
ejecutado al aplicar un caso de prueba. Esta medida se puede usar para detallar
que tan efectiva es una prueba, ya que si con los casos ejecutados, por ejemplo,
solo se ha cubierto un 50% del código, indica que la prueba se hizo a medias
porque hay otro 50% de código que no se ha probado y por tanto es susceptible a
fallos.
ECLEMMA: HERRAMIENTA PARA PRUEBAS DE COBERTURA
EclEmma es una herramienta de cobertura de código para Eclipse, basada en
Emma. Emma es un kit de herramientas de código abierto, que fue diseñado
exclusivamente para determinar el porcentaje de cobertura de código cuando se
realiza una prueba o cuando simplemente se ejecuta el código. La cobertura de
código es una medida que determina el porcentaje de código que se ha ejecutado
bajo condiciones específicas. Con las pruebas de caja blanca lo que buscamos es
encontrar fragmentos del código del programa que no son ejecutados por los
casos de pruebas. El objetivo principal es procurar que la ejecución de las pruebas
de caja blanca cubra el 100% del código.
Si encontramos que el resultado de las pruebas es menor al 100%, debemos
ejecutar otros casos de prueba para intentar llegar a un umbral muy cercano o
igual al 100%. Si aun así no conseguimos cubrir la totalidad de las sentencias
ejecutadas, se debe verificar si aquel pequeño porcentaje de código que no se ha
cubierto es totalmente necesario, pues quizás se podría prescindir de él ya que
podría ser un segmento inalcanzable por el algoritmo.
Por lo general, las pruebas terminan antes de alcanzar el 100%, pues procurar
cubrir el total de las sentencias puede ser un trabajo excesivamente arduo y
costoso.
El nivel de cobertura aceptable para una prueba depende de lo crítico que sea el
código que está siendo probado. En algunos casos una cobertura del 60-80% de
código puede ser aceptable. Pero en otros casos, como en el software
desarrollado para llevar a cabo procesos en empresas donde la confiabilidad de
los datos y la integridad de ellos sean de vital importancia, la cobertura de código
debe procurarse a un 100%. Un ejemplo muy común, es el software que se
desarrolla para el manejo de cuentas de un banco. Obligatoriamente, antes de
liberarse un programa de esta índole, se debe garantizar que la cantidad de
código que cubrió el desarrollo de las pruebas este muy cerca del umbral del
100%, pues no se puede descartar que en la mínima porción de código que no se
probó, se pueda presentar un error que implique grandes pérdidas para el banco o
para los clientes.
La cobertura requerida suele incrementar cuando se desarrollan aplicaciones
donde el control de los datos es muy delicado, por ejemplo: aplicaciones
hospitalarias, aplicaciones para centrales nucleares o aplicaciones que necesitan
cálculos precisos como aplicaciones bancarias y aplicaciones militares; también de
acuerdo al ámbito previsto de distribución del software desarrollado, ya que si un
software se distribuye a nivel mundial, de presentarse un error sería muy costoso
redistribuirlo de nuevo. Incluso a estos niveles suele ser necesario replantear el
sistema de pruebas de la aplicación y tal vez optar por métodos de verificación
formal de los algoritmos.
JUNIT, ECLEMMA Y ECLIPSE IDE
En la última versión de Eclipse (Eclipse 3.5.2 Galileo, al fecha de este documento),
viene integrada de facto la última versión de JUnit (JUnit v. 4), aunque también
viene integrada por defecto la versión anterior. Se dice que la herramienta JUnit
está integrada al IDE porque no es necesario descargar e instalar ningún plug-in
para tener esta funcionalidad (para anteriores versiones de Eclipse era necesario).
Tal como vimos en la primera unidad de este modulo, todas las herramientas del
JDK están por defecto integradas en el IDE, sin embargo JUnit no es una
herramienta que hace parte del JDK, ya que JUnit es el resultado del trabajo de
un equipo de desarrollo, quienes hicieron de esta herramienta una de las más
populares para construir y aplicar pruebas unitarias, lo que ocasionó que
progresivamente cada vez mas desarrolladores la usaran en sus proyectos. Por tal
motivo las últimas versiones de eclipse traen integrada esta herramienta, con el
objetivo de ofrecer a los desarrolladores un entorno de desarrollo que cumpla con
sus requerimientos y expectativas. Sin embargo, JUnit también puede ser usada
por medio de líneas de comandos, así como son usadas algunas herramientas del
JDK, tal como lo aprendimos en la primera unidad de este modulo.
Aunque las diferencias entre las versión 3 y 4 de JUnit son importantes, en el
desarrollo de esta unidad solo tendremos en cuenta para los ejemplos y las
actividades practicas la versión 4, por tal motivo no entraremos en detalles del
funcionamiento de la versión 3.
El manejo de JUnit y EclEmma desde Eclipse es muy sencillo e intuitivo. Cuando
se decide realizar un caso de prueba para una clase determinada, el asistente de
configuración proporciona diferentes opciones para la creación del caso de
prueba. Sin embargo, es importante aclarar que aunque Eclipse permita crear
estas clases de casos de prueba de manera automática, no implementa
funcionalidad a ninguno de los métodos prueba que se desean ejecutar, pues
esta, es directamente la función del encargado de elaborar o implementar las
pruebas.
A diferencia de JUnit, EclEmma es un plug-in que debe ser descargado e
integrado al entrono de desarrollo para acceder a su funcionalidad.
De acuerdo a lo anterior, la práctica y desarrollo de los contenidos de esta unidad,
les permitirá comprender mejor la funcionalidad y el manejo de JUnit y EclEmma
desde el IDE Eclipse.
top related