Está en la página 1de 40

Tema 6.

Pruebas y depuración de programas

1. Pruebas en el proceso de desarrollo de software


1.1. Planificación de pruebas a lo largo del ciclo de desarrollo
1.2. Técnicas comunes de pruebas de unidad
1.3. Técnicas comunes de pruebas de sistema

2. Herramientas integradas en los IDE para realización de


pruebas
2.1. Automatización de pruebas unitarias

3. Documentación de las pruebas


4. La depuración de programas
4.1. Concepto de depuración
4.2.Puntos de ruptura y examinadores de variables
4.3.La depuración en los IDE

5. Ejercicios
1. Pruebas en el proceso de desarrollo de software

1.1 Planificación de pruebas a lo largo del ciclo de desarrollo

Situación
BK Programación se encuentra desarrollando la primera versión de la
aplicación de gestión hotelera.
Ada, la supervisora de proyectos de BK Programación, se reúne con
Juan y María para empezar a planificar el diseño y realización de
pruebas sobre la aplicación,
Ana se va a encargar, de ir probando las distintas partes de la
aplicación que se van desarrollando. Para ello usará casos de prueba
diseñados por Juan, y evaluará los resultados.
Juan evaluará los resultados de las pruebas realizadas por Ana, y en su
caso, realizará las modificaciones necesarias en el proyecto.

Todos en la empresa están inmersos en el desarrollo de la aplicación


de gestión hotelera. Para garantizar la corrección del desarrollo,
Ada propone establecer la planificación de las pruebas. ¿Por qué hay
que probar el software? ¿Es necesario seguir un orden concreto en
la realización de pruebas? ¿Qué pruebas se realizan?

Durante todo el proceso de desarrollo de software, desde la fase de diseño,


en la implementación y una vez desarrollada la aplicación, es necesario
realizar un conjunto de pruebas ( Proceso que permite verificar y revelar la
calidad de un producto software. Se utilizan para identificar posibles fallos
de implementación, calidad o usabilidad de un programa ), que permitan
verificar que el software que se está creando, es correcto y cumple con las
especificaciones impuesta por el usuario.

En el proceso de desarrollo de software, nos vamos a encontrar con un


conjunto de actividades, donde es muy fácil que se produzca un error
humano. Estos errores humanos pueden ser: una incorrecta especificación
de los objetivos, errores producidos durante el proceso de diseño y errores
que aparecen en la fase de desarrollo.

Mediante la realización de pruebas de software, se van a realizar las tareas


de verificación (Proceso por el que se comprueba que el software cumple los
requisitos especificados) y validación (Proceso que comprueba si el
software hace lo que el usuario deseaba). La verificación es la comprobación
que un sistema o parte de un sistema, cumple con las condiciones impuestas.
Con la verificación se comprueba si la aplicación se está construyendo
correctamente. La validación es el proceso de evaluación del sistema o de
uno de sus componentes, para determinar si satisface los requisitos
especificados.

Para llevar a cabo el proceso de pruebas, de manera eficiente, es necesario


implementar una estrategia de pruebas. Siguiendo el Modelo en Espiral
(Modelo de ciclo de vida del software, en el que las actividades se
conforman en una espiral. Cada bucle o iteración representa un conjunto de
actividades), las pruebas empezarían con la prueba de unidad, donde se
analizaría el código implementado y seguiríamos en la prueba de integración,
donde se prestan atención al diseño y la construcción de la arquitectura del
software. El siguiente paso sería la prueba de validación, donde se
comprueba que el sistema construido cumple con lo establecido en el análisis
de requisitos de software. Finalmente se alcanza la prueba de sistema que
verifica el funcionamiento total del software y otros elementos del sistema.

1.2 Técnicas comunes de pruebas de unidad

A.- Prueba de la caja blanca o transparente

Son aquellas pruebas que permiten examinar la estructura interna del


programa. Es decir, se fijan en el código, si los bucles, las sentencias
condicionales, los puntos de toma de decisión, etcétera funcionan
correctamente, y emiten resultados para todos los caminos que se puedan
seguir.

Este tipo de pruebas serán realizadas solamente por los programadores o


por personas que conozcan el código, los datos y los flujos.
Las pruebas se basan en el criterio de cobertura, que será una medida
porcentual de ¿cuánto código se ha cubierto?
Las pruebas de caja blanca intentan garantizar que:
• Se ejecutan al menos una vez todos los caminos independientes de cada
módulo
• Se utilizan las decisiones en su parte verdadera y en su parte falsa
• Se ejecuten todos los bucles en sus límites
• Se utilizan todas las estructuras de datos internas

A.1 Prueba del camino básico

Es una técnica de caja blanca que propuso inicialmente Tom MacCabe.


• Permite que el diseñador de casos de prueba obtenga una medida de la
complejidad lógica de un diseño procedimental y que use esta medida como
guía para definir un conjunto básico de rutas de ejecución.
• Los casos de prueba deben garantizar que se ejecuta cada instrucción del
programa por lo menos una vez durante la prueba
• Conceptos
– Complejidad ciclomática
– Conjunto de caminos independientes
– Casos de prueba
Para la realización de estas pruebas es conveniente construir un diagrama
de flujo de control y sobre él escoger una serie de caminos lógicos que se
van a ejecutar usando distintos datos para comprobar el correcto
funcionamiento del código.
Los diagramas de flujo se pueden representar como sigue:

1º Grafo de flujo de un programa:

https://www.youtube.com/watch?v=9N5vPeSWRfQ

Ejemplo grafo de flujo de un programa:

https://www.youtube.com/watch?v=LACAygzhCYw

2º Complejidad Ciclomática:

https://www.youtube.com/watch?v=GVegCwwfBZ0

3º Prueba del camino básico:


https://www.youtube.com/watch?v=iILl-n57IEs

A continuación, se muestra un ejemplo basado en un diagrama de flujo que


representa la estructura de control del programa.

• Cada nodo representa una o más sentencias procedimentales


• Un solo nodo puede corresponder a una secuencia de pasos del proceso y
a una decisión.
• Las flechas (aristas) representan el flujo de control.
Cualquier representación del diseño procedimental se puede traducir a un
grafo de flujo.
Si en el diseño procedimental se utilizan condiciones compuestas, la
generación del grafo de flujo tiene que descomponer las condiciones
compuestas en condiciones sencillas, tal y como muestra la figura siguiente.

If a or b

Then

Procedimiento x

Else

Procedimiento y

End_if

y x
Complejidad Ciclomática de McCabe

https://www.youtube.com/watch?v=iILl-n57IEs

El número de caminos que se pueden seguir para la realización de las


pruebas es infinito, debido a esto y para acotar el número de caminos de
prueba aparece el término COMPLEJIDAD CICLOMÁTICA.
La Complejidad Ciclomática sirve para indicar el número de caminos
independientes que existen en un grafo. PROPORCIONA EL LÍMITE
SUPERIOR DEL NÚMERO NECESARIO DE CASOS DE PRUEBA PARA
GARANTIZAR QUE CADA INSTRUCCIÓN DEL PROGRAMA SE HAYA
EJECUTADO POR LO MENOS UNA VEZ.
La complejidad ciclomática, representada por V(G) se puede calcular de tres
formas distintas:
1. V(G) = a –n +2 , donde
a: número de arcos o aristas del grafo
n: número de nodos
2. V(G) = r, donde
r: el número de regiones cerradas del grafo
3. V(G) = c + 1
c: número de nodos de condición o de orden
Los caminos independientes del grafo son los que deben utilizarse como
casos de prueba.
Según la figura anterior Figura “Ciclos”. La complejidad ciclomática sería:
• Como el grafo tiene cuatro regiones, V(G) = 4
• Como el grafo tiene 11 aristas y 9 nodos, V(G) = 11 - 9 - 2 = 4
• Como el grafo tiene 3 nodos predicado, V(G) = 3 + 1 = 4

A partir del valor de la complejidad ciclomática obtenemos el número de


caminos independientes, que nos dan un valor límite para el número de
pruebas que tenemos que diseñar.
o En el ejemplo, el número de caminos independientes es 4, y los caminos
independientes son:
• 1-11
• 1-2-3-4-5-10-1-11
• 1-2-3-6-7-9-10-1-11
• 1-2-3-6-8-9-10-1-11

Pasos del diseño de pruebas mediante el camino básico


1. Dibujar el grafo de flujo Tomando como base el diseño o el propio código
del programa dibujar el grafo de flujo.
2. Determinar la complejidad ciclomática del grafo
3. Determinar un conjunto de caminos linealmente independientes
4. Preparar los casos de prueba
Para cada uno de los caminos del grafo anterior, preparar los casos de
prueba.

Ejemplo:
Se trata de identificar un camino básico e ir haciendo variaciones sobre
este, por ejemplo:
_ 1 – 2 – 11
_ 1 – 2 – 3 – 4 – 10 – 2
1 – 2 – 3 – 5 – 10 – 2
_1–2–3–5–6–7-9
_1–2–3–5–6–8-9

Prueba de bucles
Los bucles son la piedra angular de la inmensa mayoría de los algoritmos
implementados en software, por lo que tenemos que prestarles una atención
especial a la hora de realizar la prueba del software.
La prueba de bucles es una técnica de prueba de caja blanca que se centra
en la validez de las construcciones de los bucles.
Se pueden definir cuatro tipos de bucles diferentes:
o Simples
o Concatenados
o Anidados
o No estructurados

Bucles simples:
Para este tipo de bucles se deben llevar a cabo los siguientes casos de
prueba - considerando que n, es el número máximo de pasos permitidos por
el bucle:
a. Pasar por alto totalmente el bucle
b. Pasar una sola vez por el bucle
c. Pasar dos veces por el bucle
d. Hacer “m” pasos por el bucle con m<n
e. Hacer n-1 , n y n+1 pasos por el bucle

Bucles anidados. Cuando existen bucles anidados se llevan a cabo los


siguientes caminos de prueba:

a. Comenzar por el bucle más interior. Establecer los demás bucles en sus valores
mínimos.
b. Llevar a cabo las pruebas de bucles simples para el bucle más interior, mientras
se mantienen los parámetros de iteración (por ejemplo, contadores de bucles) de
los bucles externos en sus valores mínimos. Añadir otras pruebas para valores de
fuera de rango o excluidos
c. Progresar hacia fuera, llevando a cabo pruebas para el siguiente bucle, pero
manteniendo todos los bucles externos en sus valores mínimos y los demás bucles
anidados en sus valores “típicos”.
d. Continuar hasta que se hayan probado todos los bucles.

Bucles concatenados:
Probar los bucles concatenados mediante las técnicas de prueba para bucles
simples, considerándolos como bucles independientes

Bucles no estructurados
Siempre que sea posible, esta clase de bucles debe diseñarse nuevamente para
reflejar el uso de las construcciones de programación estructurada

Ejemplos de pruebas de caja blanca:

http://ing-software3.blogspot.com/2013/

Ejemplo 1

PROCEDURE Media;
* Este procedimiento calcula la media de 100 o menos números que se encuentran
entre unos límites; también calcula el total de entradas y el total de números
válidos.
INTERFACE RETURNS media, total.entrada, total.valido;
INTERFACE ACEPTS valor, minimo, maximo;
TYPE valor [1:100] IS INTEGER ARRAY;
TYPE media, total.entrada, total.validom, inimo, maximo, suma IS INTEGER;
TYPE i IS INTEGER;
i=1
total.entrada = total.valido = 0
suma = 0
DO WHILE VALOR [i] <> ‐999 and total.entrada < 100
Incrementar total.entrada en 1;
IF valor [i] >= minimo AND valor [i] <= maximo
THEN
incrementar total.valido en 1;
suma = suma + valor [i];
ELSE ignorar
END IF
Incrementar i en 1;
END DO
IF total valido > 0
THEN media = suma/total.valido
ELSE media = ‐999
END IF
END MEDIA
1

1 4
0

1
11
2 5
2

1
3 6

7 8

V(g)=a-n+2= 17-13+2=6
V(g)=c+1 =5+1= 6 los 5 nodos son 2,3,5,6 y10
B.- Prueba de la Caja Negra

Las pruebas de Caja Negra son aquellas que comprueban el funcionamiento


de un componente software a través de su interfaz, es decir, de sus
entradas y salidas, sin entrar a ver su funcionamiento interno.

Estas pruebas se pueden ver desde un enfoque funcional del sistema, ya que
se diseñan los casos de prueba y los datos de prueba a partir de las
especificaciones funcionales del sistema, se busca probar completamente:
 Las funciones realizadas por el sistema
 El cumplimiento de los objetivos del sistema
 Las reacciones del sistema ante los estímulos exteriores
Para comprobar el correcto funcionamiento del sistema se:
 Introducen valores límites
 Se introducen datos erróneos, valores susceptibles a crear
problemas.
 Se introducen datos equivalentes

Estas pruebas ayudan a encontrar errores en las siguientes categorías:


 Funciones incorrectas o ausentes
 Errores de interfaz
 Errores en estructuras de datos o en acceso a base de datos
externas
 Errores de rendimiento
 Errores de inicialización y de terminación

B.1 Clases de Equivalencia

El problema que tienen las pruebas de Caja Negra es que los rangos de
datos son muy amplios y sería muy costoso probar todos los valores de un
rango, para evitar realizar esto se agrupan los datos en lo que llamamos
CLASES DE EQUIVALENCIA.
Tests Funcionales. Clases de equivalencia y valores límite:

https://www.youtube.com/watch?v=PmdFMDZVmmM

https://www.youtube.com/watch?v=pAVc6SY__cA&t=4s

Esta técnica trata cada parámetro como un modelo algebraico donde unos
datos son equivalentes a otros. Si logramos partir un rango excesivamente
amplio de posibles valores reales a un conjunto reducido de clases de
equivalencia, entonces es suficiente probar un caso de cada clase, pues los
demás datos de la misma clase son equivalentes.

Para crear las clases de equivalencia existe una norma que es la que sigue:
 si un parámetro de entrada debe estar comprendido en un cierto
rango, aparecen 3 clases de equivalencia: por debajo, en el propio
rango y por encima del rango.
 si una entrada requiere un valor concreto, aparecen 3 clases de
equivalencia: por debajo, en el rango y por encima del rango.
 si una entrada requiere un valor de entre los de un conjunto, aparecen
2 clases de equivalencia: en el conjunto o fuera de él.
 si una entrada es booleana, hay 2 clases: si o no.
 los mismos criterios se aplican a las salidas esperadas: hay que
intentar generar resultados en todas y cada una de las clases.
Por ejemplo: Si la especificación del problema es la siguiente:
 Código: número de 3 dígitos que no empieza ni por 0, ni por 1.
 Identificador: 9 caracteres.
 Ordenes posibles: "cheque", "depósito", "pago factura", "retirada de
fondos"
 Edad: Entre 18 y 65
Condición de Entrada Clases de equivalencia válidas Clases de equivalencia inválidas
Código (1) 200 > codigo <999 (2) Codigo < 200
(3) Codigo > 999
(4) No es un número

Identificador 9 caracteres Menos de 9 caracteres


Más de 9 caracteres

Orden (1) “cheque” (5) Ninguna orden válida


(2) “depósito”
(3) “pago factura”
(4) “retirada de fondos”

Edad (1) 18 > = edad <=65 (2) edad < 18


(3) edad > 65
(4) No es número

Análisis de valores límite


Esta técnica es complementaria a las Clases de Equivalencia y explora las
condiciones límites de un programa.
Es más probable que una función falle cuando los valores de entrada a la
misma se sitúan en el límite de valores del rango de la función.
Por ejemplo, si una variable toma valores entre 20 y 500 se deben escoger
los siguientes casos de prueba:
_ 19 Justo debajo del rango
_ 20 El límite inferior del rango
_ 21 Dentro del rango
_ Un valor cualquiera de la mitad del rango
_ 499 Valor debajo del límite superior
_ 500 El límite superior
_ 501 Justo por encima del rango superior
Si los recursos lo permiten, se podrían añadir las siguientes pruebas
adicionales:
_ Valores negativos
Al menos un valor positivo menor que 19
Algún valor muy superior a 500

Documentación

Deberemos documentar las pruebas realizadas. Se muestra un posible


esquema de documento.

Núm. Descripción . Datos Resultado Resultado Comentarios


Prueba Prueba entrada esperado obtenido

Ejemplo de ejercicio de caja negra:

Un programa recibe como entrada un número entero y positivo de mínimo 2


cifras y de máximo 9 cifras y devuelve el número resultante de invertir sus
cifras. Si no se introduce un valor acorde a lo descrito (por
ejemplo: flotantes y/o caracteres, valores fuera de rango, etc.), el
módulo devolverá el valor “error”.
Genere la tabla de clases de equivalencia y los casos de prueba (no olvide el
análisis de valores límite).

Tipo Condición Clases Correctas Clases erróneas


A Nº de parámetros 1. {n = 1} 2.1. {n<1}
2.2. {n>1}.
B valor Tipo de los parámetros 3. {x N} 4. {x N}
C Intervalo 5. {x > 9, x < 1000000000} 6.1. {x <10 }
6.2. {x > 999999999}

Entradas Salidas Clases Valores Salidas


Cubiertas Límites
Clase (456248) (842654) 1, 3, 5 (10) 1
correcta (12) 21
(999999999) 999999999
(999999998) 899999999
Clases () Error 2.1
Erroneas
(2148, 215879, Error 2.2 (2148,
345872) 215879)
(254,3689) Error 4
(‘z’) Error 4
(8) Error 6.1 (9)
(10000000001) Error 6.2 (1000000000)

1.3 Pruebas del sistema


a.- Pruebas de Integración
Son aquellas pruebas que comprueban que los componentes probados
individualmente funcionan correctamente trabajando juntos, es decir,
integrados. Estas pruebas se realizan de forma gradual y se pueden realizar
de dos formas:
1. Incremental (ascendente o descendente)
Se combina el siguiente módulo que se quiere probar con el conjunto de
módulos que ya están probados.

2. No incremental
Se prueba cada módulo por separado, y luego se integran todos a la vez y se
prueba el sistema completo.
b.- Pruebas de Sistema
Estas pruebas consideran el producto final al completo, es decir, al sistema
final, comprobando que módulos probados individualmente funcionan
correctamente cuando trabajan juntos, y que los fallos en el sistema pueden
ser debidos a una errónea comunicación entre subsistemas.
2. Herramientas integradas en los IDE para realización de
pruebas
Antonio está un poco confuso. La aplicación que están diseñando Juan y
María es ya de cierta envergadura y se pregunta por la labor ingente
que queda, solo para probar todos los componentes de la aplicación.
María le tranquiliza y le comenta que los Entornos de Desarrollo
actuales, incorporan herramientas que realizan las pruebas de cada
método, de forma automática.
Las pruebas unitarias, o prueba de la unidad, tienen por objetivo probar el
correcto funcionamiento de un módulo de código. El fin que se persigue, es
que cada módulo funciona correctamente por separado.

Posteriormente, con la prueba de integración, se podrá asegurar el correcto


funcionamiento del sistema.

Una unidad es la parte de la aplicación más pequeña que se puede probar. En


programación procedural, una unidad puede ser una función o procedimiento,
En programación orientada a objetos, una unidad es normalmente un método.

Con las pruebas unitarias se debe probar todas las funciones o métodos no
triviales de forma que cada caso de prueba sea independiente del resto.

En el diseño de los casos de pruebas unitarias, habrá que tener en cuenta


los siguientes requisitos:

1 Automatizable: no debería requerirse una intervención manual.


2 Completas: deben cubrir la mayor cantidad de código.
3 Repetibles o Reutilizables: no se deben crear pruebas que sólo
puedan ser ejecutadas una sola vez.
4 Independientes: la ejecución de una prueba no debe afectar a la
ejecución de otra.
5 Profesionales: las pruebas deben ser consideradas igual que el código,
con la misma profesionalidad, documentación, etc.
El objetivo de las pruebas unitarias es aislar cada parte del programa y
demostrar que las partes individuales son correctas. Las pruebas
individuales nos proporcionan cinco ventajas básicas:

1. Fomentan el cambio: Las pruebas unitarias facilitan que el programador


cambie el código para mejorar su estructura, puesto que permiten hacer
pruebas sobre los cambios y así asegurarse de que los nuevos cambios no
han introducido errores.
2. Simplifica la integración: Puesto que permiten llegar a la fase de
integración con un grado alto de seguridad de que el código está
funcionando correctamente.
3. Documenta el código: Las propias pruebas son documentación del código
puesto que ahí se puede ver cómo utilizarlo.
4. Separación de la interfaz y la implementación: Dado que la única
interacción entre los casos de prueba y las unidades bajo prueba son las
interfaces de estas últimas, se puede cambiar cualquiera de los dos sin
afectar al otro.
5. Los errores están más acotados y son más fáciles de localizar: dado
que tenemos pruebas unitarias que pueden desenmascararlos.
Herramientas para Java.
Entre las herramientas que nos podemos encontrar en el mercado, para
poder realizar las pruebas, las más destacadas serían:

 Jtiger:
1 Framework de pruebas unitarias para Java (1.5).
2 Es de código abierto.
3 Capacidad para exportar informes en HTML, XML o texto plano.
4 Es posible ejecutar casos de prueba de Junit mediante un plugin.
5 Posee una completa variedad de aserciones como la comprobación de
cumplimiento del contrato en un método.
6 Los metadatos (Conjunto de datos que se utilizan para describir
otros datos) de los casos de prueba son especificados como anotaciones del
lenguaje Java
7 Incluye una tarea de Ant para automatizar las pruebas.
8 Documentación muy completa en JavaDoc, y una página web con toda
la información necesaria para comprender su uso, y utilizarlo con IDE como
Eclipse.
9 El Framework (Estructura conceptual y tecnológica de soporte
definida, normalmente con módulos de software concretos, con base en la
cual otro proyecto de software puede ser organizado y desarrollado ) incluye
pruebas unitarias sobre sí mismo.
 TestNG:
10 Esta inspirado en JUnit y NUnit.
11 Está diseñado para cubrir todo tipo de pruebas, no solo las unitarias,
sino también las funcionales, las de integración ...
12 Utiliza las anotaciones de Java 1.5 (desde mucho antes que Junit).
13 Es compatible con pruebas de Junit.
14 Soporte para el paso de parámetros a los métodos de pruebas.
15 Permite la distribución de pruebas en maquinas esclavas.
16 Soportado por gran variedad de plug‐ins (Eclipse, NetBeans, IDEA ...)
17 Las clases de pruebas no necesitan implementar ninguna interfaz ni
extender ninguna otra clase.
18 Una vez compiladas las pruebas, estas se pueden invocar desde la
línea de comandos con una tarea de Ant o con un fichero XML.
19 Los métodos de prueba se organizan en grupos (un método puede
pertenecer a uno o varios grupos).
 Junit:
20 Framework de pruebas unitarias creado por Erich Gamma y Kent
Beck.
21 Es una herramienta de código abierto.
22 Multitud de documentación y ejemplos en la web.
23 Se ha convertido en el estándar de hecho para las pruebas unitarias
en Java.
24 Soportado por la mayoría de los IDE como eclipse o Netbeans.
25 Es una implementación de la arquitectura xUnit para los frameworks
de pruebas unitarias.
26 Posee una comunidad mucho mayor que el resto de los frameworks de
pruebas en Java.
27 Soporta múltiples tipos de aserciones.
28 Desde la versión 4 utiliza las anotaciones del JDK 1.5 de Java.
29 Posibilidad de crear informes en HTML.
30 Organización de las pruebas en Suites de pruebas.
31 Es la herramienta de pruebas más extendida para el lenguaje Java.
32 Los entornos de desarrollo para Java, NetBeans y Eclipse, incorporan
un plugin para Junit.
Herramientas para otros lenguajes.
En la actualidad, nos encontramos con un amplio conjunto de herramientas
destinadas a la automatización de la prueba, para la mayoría de los lenguajes
de programación más extendidos en la actualidad. Existen herramientas
para C++, para PHP, FoxPro, etc.

Cabe destacar las siguientes herramientas:

 CppUnit:
1 Framework de pruebas unitarias para el lenguaje C++.
2 Es una herramienta libre.
3 Existe diversos entornos gráficos para la ejecución de pruebas como
QtTestRunner.
4 Es posible integrarlo con múltiples entornos de desarrollo como
Eclipse.
5 Basado en el diseño de xUnit.
 Nunit:
6 Framework de pruebas unitarias para la plataforma .NET.
7 Es una herramienta de código abierto.
8 También está basado en xUnit.
9 Dispone de diversas expansiones como Nunit.Forms o Nunit.ASP.
Junit
 SimpleTest: Entorno de pruebas para aplicaciones realizadas en PHP.
 PHPUnit: framework para realizar pruebas unitarias en PHP.
 FoxUnit: framework OpenSource de pruebas unitarias para
Microsoft Visual FoxPro
 MOQ: Framework para la creación dinámica de objetos simuladores
(mocks).
Automatización de la prueba.

Caso práctico
Juan está realizando pruebas de la unidad, es decir, comprueba el
correcto funcionamiento de los métodos que ha implantado. Para ello,
utiliza la herramienta de prueba incorporadas en el entorno de
desarrollo. En su caso, ya que está utilizando NetBeans, se decanta
por Junit. Ana está muy interesada en conocer esta herramienta, que
ayuda notablemente en el proceso de pruebas.

Las pruebas de software son parte esencial del ciclo de desarrollo. La


elaboración y mantenimiento de unidad, pueden ayudarnos a asegurar que los
métodos individuales de nuestro código, funcionan correctamente. Los
entorno de desarrollo, integran frameworks, que permiten automatizar las
pruebas.

En el caso de entornos de desarrollo para Java, como NetBeans y Eclipse,


nos encontramos con el framework JUnit. JUnit es una herramienta de
automatización de pruebas que nos permite de manera rápida y sencilla,
elaborar pruebas. La herramienta nos permite diseñar clases de prueba,
para cada clase diseñada en nuestra aplicación. Una vez creada las clases de
prueba, establecemos los métodos que queremos probar, y para ello
diseñamos casos de prueba. Los criterios de creación de casos de prueba,
pueden ser muy diversos, y dependerán de lo que queramos probar.

Una vez diseñados los casos de prueba, pasamos a probar la aplicación. La


herramienta de automatización, en este caso Junit, nos presentará un
informe con los resultados de la prueba (imagen anterior). En función de los
resultados, deberemos o no, modificar el código.
Uso de JUNIT en NetBeans

INTRODUCCIÓN
Los entornos de desarrollo más extendidos, que se utilizan para
implementar aplicaciones Java, tales como NetBeans o Eclipse, incorporan
un plugin para trabajar con Junit

Junit nos va a servir para realizar pruebas unitarias de clases escritas en


Java, dentro de un entorno de pruebas. Es un framework con muy pocas
clases fácil de aprender y de utilizar.

Una vez que hemos diseñado nuestra aplicación, y la hemos depurado,


procedemos a probarla. En el caso del ejemplo, disponemos de una clase, de
nombre calculadora, donde se han definido una serie de métodos.

El objetivo va a ser el diseño y ejecución de algunos casos de prueba.

INICIO DE JUNIT
Para iniciar Junit, seleccionada en la ventana de proyectos la clase a probar,
abrimos el menú contextual y seleccionamos Herramientas > Crear
pruebas Junit.

Nos aparece un formulario donde nos da a elegir entre JUnit 3.x y JUnit
4.x. Son las dos versiones de JUnit disponibles en NetBeans 7.1. En nuestro
caso, elegimos JUnit 4.x.

6 Puesto que vamos a probar la clase ejemplo, por convenio es


recomendable llamar a la clase de prueba calculadoraTest. Esta clase se va a
insertar en un nuevo paquete de nuestro proyecto, denominado Paquete de
prueba.
Como se aprecia en el formulario,
JUnit va a generar los métodos que
aparecen seleccionados. En nuestro
caso lo vamos a dejar tal cual, aunque
luego van a ser modificados en el
código.

Al pulsar el botón Aceptar nos


aparecen un nueva clase de nombre
calculadoraTest, que contiene los
métodos que estaban seleccionados en el formulario anterior, con un código
prototipo. Es en ese código en el que el programador creará sus casos de
prueba.

El diseño de los casos de prueba, requiere que se establezcan criterios que


garanticen que esa prueba tiene muchas probabilidades de encontrar algún
error no detectado hasta el momento.

CASOS DE PRUEBA
En primer lugar, vamos a ver la función de los Inicializadores y
Finalizadores. El método SetUp y el método tearDown, se utilizan para
inicializar y finalizar las condiciones de prueba, como puede ser la creación
de un objeto, inicialización de variables, etc. En algunos casos, no es
necesario utilizar estos métodos, pero siempre se suelen incluir.

El método setUp es un método de inicialización de la prueba y se ejecutan


antes de cada caso de prueba, en la clase de prueba. Este método no es
necesario para ejecutar pruebas, pero si es necesario para inicializar
algunas variables antes de iniciar la prueba.

El método tearDown es un método finalizador de prueba, y se ejecutará


después de cada test en la clase prueba. Un método finalizador no es
necesario para ejecutar las pruebas, pero si necesitamos un finalizador para
limpiar algún dato que fue requerido en la ejecución de los casos de prueba.

En segundo lugar, es necesario conocer las aserciones. Los método


assertXXX(), se utilizan para hacer las pruebas. Estos métodos, permiten
comprobar si la salida del método que se está probando, concuerda con los
valores esperados. Las principales son:

AssertTrue() evalúa una expresión booleana. La prueba pasa si el valor de la


expresión es true.

assertFalse() evalúa una expresión booleana. La prueba pasa si el valor de la


expresión es false.

AssertNull() verifica que la referencia a un objeto es nula.

assertNotNull() verifica que la referencia a un objeto es no nula.

AssertSame() compara dos referencias y asegura que los objetos


referenciados tienen la misma dirección de memoria. La prueba pasa si los
dos argumentos son el mismo objeto o pertenecen al mismo objeto.
assertNotSame() Compara dos referencias a objetos y asegura que ambas
apuntan a diferentes direcciones de memoria. La prueba pasa si los dos
argumentos suplidos son objetos diferentes o pertenecen a objetos
distintos.

assertEquals() Se usa para comprobar igualdad a nivel de contenidos. La


igual de tipos primitivos se compara usando “==”, la igual entre objetos se
compara con el método equals(). La prueba pasa si los valores de los
argumentos son iguales.

fails() causa que la prueba falle inmediatamente. Se puede usar cuando la


prueba indica un error o cuando se espera que el método que se está
probando llame a una excepción.
En este punto, nos disponemos a diseñar los métodos que necesitemos para
los casos de prueba.

package calculadora;

import org.junit.*;
import static org.junit.Assert.*;

public class CalculadoraTest {

public CalculadoraTest() {
}

@BeforeClass
public static void setUpClass()
throws Exception {
}

@AfterClass
public static void tearDownClass()
throws Exception {
}

@Before
public void setUp() {
}

@After
public void tearDown() {
}
@Test
public void testAdd() {
System.out.println("add");
int x = 0;
int y = 0;
int expResult = 0;
int result = Calculadora.add(x, y);
assertEquals(expResult, result);
// TODO review the generated test code and remove the default call
to fail.
fail("The test case is a prototype.");
}
@Test
public void testSubtract() {
System.out.println("Subtract");
int x = 0;
int y = 0;
int expResult = 0;
int result = Calculadora.Subtract(x, y);
assertEquals(expResult, result);
// TODO review the generated test code and remove the default call
to fail.
fail("The test case is a prototype.");
}
@Test
public void testSquare() {
System.out.println("Square");
int x = 0;
int expResult = 0;
int result = Calculadora.Square(x);
assertEquals(expResult, result);
// TODO review the generated test code and remove the default call
to fail.
fail("The test case is a prototype.");
}

@Test
public void testMain() {
System.out.println("main");
String[] args = null;
Calculadora.main(args);
// TODO review the generated test code and remove the default call
to fail.
fail("The test case is a prototype.");
}
}

Una vez que tenemos el fichero calculadoratest.java le damos al icono


ejecutar/probar archivo y nos salen los fallos que tenemos. Comprobamos
que los fallos que hay están en fail… Una vez que lo observamos los fail los
ponemos como comentarios con doble barra del 7 // y comprobamos que no
hay ningún error. Para ver que hay errores ponemos valores que no se
puedan dar en las operaciones de suma resta y elevado al cuadrado
3. Documentación de las pruebas

Como en otras etapas y tareas del desarrollo de aplicaciones, la


documentación de las pruebas es un requisito indispensable para su correcta
realización. Unas pruebas bien documentadas podrán también servir como
base de conocimiento para futuras tareas de comprobación.

Las metodologías actuales, como Métrica v.3 (Metodología de Planificación,


Desarrollo y Mantenimiento de sistemas de información. Métrica v3 se
puede usar libremente, con la única restricción de citar la fuente de su
propiedad intelectual, que es el Ministerio de Presidencia ), proponen que la
documentación de la fase de pruebas se basen en los estándares ANSI /
IEEE sobre verificación y validación de software.

En propósito de los estándares ANSI/IEEE es describir un conjunto de


documentos para las pruebas de software. Un documento de pruebas
estándar puede facilitar la comunicación entre desarrolladores al
suministrar un marco de referencia común. La definición de un documento
estándar de prueba puede servir para comprobar que se ha desarrollado
todo el proceso de prueba de software.

Los documentos que se van a generar son:

 Plan de Pruebas: Al principio se desarrollará una planificación


general, que quedará reflejada en el "Plan de Pruebas". El plan de
pruebas se inicia el proceso de Análisis del Sistema.
 Especificación del diseño de pruebas. De la ampliación y detalle del
plan de pruebas, surge el documento "Especificación del diseño de
pruebas".
 Especificación de un caso de prueba. Los casos de prueba se
concretan a partir de la especificación del diseño de pruebas.
 Especificación de procedimiento de prueba. Una vez especificado un
caso de prueba, será preciso detallar el modo en que van a ser
ejecutados cada uno de los casos de prueba, siendo recogido en el
documento "Especificación del procedimiento de prueba".
 Registro de pruebas. En el "Registro de pruebas" se registrarán los
sucesos que tengan lugar durante las pruebas.
 Informe de incidente de pruebas. Para cada incidente, defecto
detectado, solicitud de mejora, etc, se elaborará un "informe de
incidente de pruebas".
 Informe sumario de pruebas. Finalmente un "Informe sumario de
pruebas" resumirá las actividades de prueba vinculadas a uno o más
especificaciones de diseño de pruebas.

4. La depuración de programas

4.1 .Concepto de depuración

Para la informática, la depuración de un software consiste


en desarrollar una acción para detectar y solucionar problemas en
la programación. Aquellos procedimientos automatizados que buscan los
errores se conocen como depuradores.

Depurar un programa informático es una tarea tan ardua


como utópica, ya que en la práctica resulta virtualmente imposible asegurar
que todos los errores han sido detectados y corregidos. En las compañías
desarrolladoras de software existe un departamento de programadores que
se encarga de crear las aplicaciones, otro de testers que tiene la particular
responsabilidad de probarlas con la intención de toparse con fallos e
inconsistencias, y uno que también consta de programadores
(llamados debuggers), que en este caso serán quienes busquen soluciones a
los problemas hallados por los testers.

Los debuggers asumen el duro trabajo de analizar las miles de líneas


de código en busca de una explicación lógica a los errores detectados. En
este proceso, la intuición y la experiencia son esenciales, sobre todo cuando
las fechas de entrega pesan sobre sus espaldas. Comúnmente, los testers
carecen de conocimientos técnicos, por lo cual sus reportes no distan mucho
de las quejas de los consumidores, y esto no resulta beneficioso para los
programadores que los reciben, ya que su misión es dar con la razón exacta
y ofrecer una solución eficiente.

En muchos casos, a pesar de contar con decenas de personas


trabajando intensamente para depurar un programa o un juego, las
empresas se ven forzadas a “emparchar” sus productos, lo que significa
elaborar pseudosoluciones. La calidad de estos parches, así como su
repercusión en el resultado final varía de acuerdo a la capacidad de los
debuggers, al tiempo disponible, y no necesariamente impactan en la calidad
de la aplicación. De hecho, es posible que no exista ningún software que
haya sido pulido al 100%, que goce de un código limpio y absolutamente
ordenado y funcional. Afortunadamente, existe la posibilidad de publicar
actualizaciones que los consumidores pueden descargar para corregir
aquellos errores que surjan luego de la salida al mercado .

4.2.Puntos de ruptura.

Dentro del menú de depuración, nos encontramos con la opción insertar


punto de ruptura (breakpoint). Se selecciona la línea de código donde
queremos que el programa se pare, para a partir de ella, inspeccionar
variables, o realizar una ejecución paso a paso, para verificar la corrección
del código

Durante la prueba de un programa, puede ser interesante la verificación de


determinadas partes del código. No nos interesa probar todo el programa,
ya que hemos delimitado el punto concreto donde inspeccionar. Para ello,
utilizamos los puntos de ruptura.

Los puntos de ruptura son marcadores que pueden establecerse en cualquier


línea de código ejecutable (no sería válido un comentario, o una línea en
blanco). Una vez insertado el punto de ruptura, e iniciada la depuración, el
programa a evaluar se ejecutaría hasta la línea marcada con el punto de
ruptura. En ese momento, se pueden realizar diferentes labores, por un
lado, se pueden examinar las variables, y comprobar que los valores que
tienen asignados son correctos, o se pueden iniciar una depuración paso a
paso, e ir comprobando el camino que toma el programa a partir del punto de
ruptura. Una vez realiza la comprobación, podemos abortar el programa, o
continuar la ejecución normal del mismo.

Dentro de una aplicación, se pueden insertar varios puntos de ruptura, y se


pueden eliminar con la misma facilidad con la que se insertan.

Tipos de ejecución.
Para poder depurar un programa, podemos ejecutar el programa de
diferentes formas, de manera que en función del problema que queramos
solucionar, nos resulte más sencillo un método u otro. Nos encontramos con
lo siguientes tipo de ejecución: paso a paso por instrucción, paso a paso por
procedimiento, ejecución hasta una instrucción, ejecución de un programa
hasta el final del programa,

1 Algunas veces es necesario ejecutar un programa línea por línea, para


buscar y corregir errores lógicos. El avance paso a paso a lo largo de una
parte del programa puede ayudarnos a verificar que el código de un método
se ejecute en forma correcta.
2
3 El paso a paso por procedimientos, nos permite introducir los
parámetros que queremos a un método o función de nuestro programa, pero
en vez de ejecutar instrucción por instrucción ese método, nos devuelve su
resultado. Es útil, cuando hemos comprobado que un procedimiento funciona
correctamente, y no nos interese volver a depurarlo, sólo nos interesa el
valor que devuelve.
4
5 En la ejecución hasta una instrucción, el depurador ejecuta el
programa, y se detiene en la instrucción donde se encuentra el cursor, a
partir de ese punto, podemos hacer una depuración paso a paso o por
procedimiento.
6
7 En la ejecución de un programa hasta el final del programa,
ejecutamos las instrucciones de un programa hasta el final, sin detenernos
en las instrucciones intermedias.

Los distintos modos de ejecución, se van a ajustar a las


necesidades de depuración que tengamos en cada momento. Si hemos
probada un método, y sabemos que funciona correctamente, no es
necesario realizar una ejecución paso a paso en él.
En el IDE NetBeans, dentro del menú de depuración, podemos
seleccionar los modos de ejecución especificados, y algunos más. El objetivo
es poder examinar todas las partes que se consideren necesarias, de manera
rápida, sencilla y los más clara posible.

Examinadores de variables.

Durante el proceso de implementación y prueba de software, una de


las maneras más comunes de comprobar que la aplicación funciona de manera
adecuada, es comprobar que las variables vayan tomando los valores
adecuados en cada momento.
Los examinadores de variables, forman uno de los elementos más
importantes del proceso de depuración de un programa. Iniciado el proceso
de depuración, normalmente con la ejecución paso a paso, el programa
avanza instrucción por instrucción. Al mismo tiempo, las distintas variables,
van tomando diferentes valores. Con los examinadores de variables,
podemos comprobar los distintos valores que adquieren las variables, así
como su tipo. Esta herramienta es de gran utilidad para la detección de
errores.

En el caso del entorno de desarrollo NetBeans, nos encontramos con


un panel llamado Ventana de Inspección. En la ventana de inspección, se
pueden ir agregando todas aquellas variables de las que tengamos interés en
inspeccionar su valor. Conforme el programa se vaya ejecutando, NetBeans
irá mostrando los valores que toman las variables en la ventana de
inspección.

Como podemos apreciar, en una ejecución paso a paso, el programa


llega a una función de nombre potencia. Esta función tiene definida tres
variables. A lo largo de la ejecución del bucle, vemos como la variable result,
van cambiando de valor. Si con valores de entrada para los que conocemos el
resultado, la función no devuelve el valor esperado, "Examinando las
variables" podremos encontrar la instrucción incorrecta.

Los depuradores son programas que permiten analizar de manera


exhaustiva y en paso a paso, lo que pasa dentro del código de un programa.
Gracias a los depuradores es fácil probar las aplicaciones para encontrar los
posibles errores, analizando sus causas y posibles soluciones.

En el caso del IDE NetBeans, que es el que utilizamos en el ejemplo,


estas utilidades están integradas en el entorno de desarrollo.

4.3. La depuración en los IDE

Un depurador (en inglés, debugger), es un programa usado para


probar y depurar (eliminar los errores) el código fuente de un programa.
Típicamente, los depuradores también ofrecen funciones más sofisticadas
tales como correr un programa paso a paso, pausar el programa para
examinar el estado actual en cierto evento o instrucción especificada por
medio de un breakpoint, y el seguimiento de valores de algunas variables.

Algunos depuradores tienen la capacidad de modificar el estado del


programa mientras que está corriendo, en vez de simplemente observarlo.
•Los motores de depuración actuales, tales como gdb y dbx proporcionan
también interfaces basadas en línea de comandos.
5.- Ejercicios

1º.- Construya el grafo de flujo para el pseudocódigo adjunto, calcule la


complejidad ciclomática, el conjunto de caminos básico

Tenemos un vector con 10 números.


a) Visualizar cada uno de los números.
a) Comprobar si cada número es par o impar.
b) Sumar todos los números pares e ir visualizando la suma cada vez que
sumes un número.
c) Visualizar si el número impar es mayor o menor de 50.

int v[10]={12, 56, 33, 59, 8, 7, 75, 78, 44, 22};


int i=0, s=0;
1 while(i<10)
{
printf("%d\n",v[i]);
2 if(v[i]%2==0)
{
3
s+=v[i];
printf("Suma de pares: %d\n",s);
}
else
{
4
if(v[i]>=50) 5
printf("Número mayor de 50 %d\n",v[i]);
else
printf("Número menor de 50 %d\n",v[i]); 6
7
//fin if
} //fin if 8
i++;
9
} //fin while
#include "stdio.h"

int main()
{
int v[10]={12, 56, 33, 59, 8, 7, 75, 78, 44, 22};
int i=0, s=0;
while(i<10)
{
printf("%d\n",v[i]);
if(v[i]%2==0)
{
s+=v[i];
printf("Suma de pares: %d\n",s);
}
else
{
if(v[i]>=50)
printf("Número mayor de 50 %d\n",v[i]);
else
printf("Número menor de 50 %d\n",v[i]);
}
i++;
}
getchar();
return 0;
}

2º.- Construya el grafo de flujo para el pseudocódigo adjunto, calcule la


complejidad ciclomática, el conjunto de caminos básico

1. Leer x, y
2. Si (x<=0 )o(y<=0)entonces
3. Escribe “Deben ser no negativos”
Visualiza -1
4. Sino Si (x=1) o (y=1) entonces
5. Visualiza 1
6. Sino Mientras (x<>y)
7. Si (x>y) entonces
8. x=x-y
9. sino
y=y-x
10. fin Si
11. fin mientras
12visializa x
13. fin si
14. fin si

3º.- Construya el grafo de flujo para el pseudocódigo adjunto, calcule la


complejidad ciclomática,el conjunto de caminos básico y los casos de prueba
asociados. ¿Detectas alguna anomalía al definir los casos de prueba?

function obtener_media : real ; /* devuelve un real


/* Trata de realizar la media de números leídos por teclado
siempre que estén entre 20 y 50*/
/* Si los números no están en el intervalo 20-50 el programa
simplemente acumula el valor de los números leídos */
variables
n, suma, conta, suma2, total_num : integer ;
inicio
leer( n ) ;
suma := 0;
conta := 0;
total_num := 0;
suma2 := 0;
repetir
si (n >= 20 or n <= 50) {
suma := suma + n ;
conta := conta + 1
}
else {
suma2 := suma2 + n ;
total_num := total_num + 1 ;
}
leer (n) ;
hasta que n = 0 ;
obtener_media := suma / conta ;
escribir (total_num, suma2) ;
end;
4º.- Tenemos un pequeño módulo que lee una hora en formato de hora
militar e indica si la hora es correcta. Se anexa a continuación la pantalla: Si
no se introduce un valor acorde a lo descrito (por ejemplo:
flotantes y/o caracteres, valores fuera de rango, etc.), el módulo
devolverá el valor “error”.
Introduce la Hora HH:MM :

OK

Genere la tabla de clases de equivalencia y los casos de prueba (no olvide el


análisis de valores límite). Solución: n = número de parámetros A = conjunto de
valores permitidos: los números naturales más el cero. A = N U {0}.

5º.- Elaborar una batería de pruebas para un módulo que recibe como
entrada una cadena de caracteres y determina si puede ser una clave válida
o no (por lo tanto devuelve un valor lógico, verdadero o falso). Una clave se
considera válida si cumple los requisitos siguientes:

• Está formada por más de 4 y menos de 8 caracteres.


• Los caracteres permitidos son los alfabéticos a...z, A...Z, los dígitos
0...9 y los caracteres especiales ‘%’ y ‘#’.
• Contiene al menos dos alfabéticos.
• Contiene al menos un carácter que es dígito.
• El primer y último carácter deben ser alfabéticos.
• No aparece en un diccionario de palabras prohibidas (user%10, us3r
%aa, etc).
6º.- Dado el siguiente programa java:

package maximo_minimo_media;

public class Maximo_minimo_media


{
public static int maximo (int x, int y)
{
int maximo;
maximo=-500;
if(x>maximo)
maximo=x;
if(y>maximo)
maximo=y;
return maximo;
}
public static int minimo (int x, int y)
{
int minimo;

minimo=500;
if(x<minimo)
minimo=x;
if(y<minimo)
minimo=y;

return minimo;
}

public int media( int w, int x, int y, int z)


{
int media;
media=(w+x+y+z)/4;
return media;
}

public static void main(String[] args)


{
}
}
Que realiza el máximo y el mínimo de dos números así como la media de 4
números. Utilizar JUNIT para los casos de prueba y ver cuando falla y
cuando no el programa.

7º.- Utilizando la ejecución paso a paso diga cómo van cambiando los valores
de la práctica 1 del ejercicio anterior.

Notas:
 En todos los ejercicios siga el guión de las prácticas del tema
anterior.
 Suba los ejercicios en un solo archivo pdf con el nombre
practica_tema_5_apellido1_apellido2_nombre

También podría gustarte