Pruebas de Software: Métodos y Herramientas
Pruebas de Software: Métodos y Herramientas
Software
ED FP-GS; Tema3:PruebasDelSoftware
© Gerardo Martín Esquivel, Febrero de 2023
Algunos derechos reservados.
Este trabajo se distribuye bajo la Licencia "Reconocimiento-No comercial-
Compartir igual 3.0 Unported" de Creative Commons disponible en
http://creativecommons.org/licenses/by-nc-sa/3.0/
Entornos de Desarrollo 1º DAW / 1º DAM
GRAFO DE FLUJO
En un grafo de flujo los nodos son círculos que representan un grupo de sentencias que
siempre se ejecutan juntas y las flechas indican la dirección que sigue el flujo entre los nodos.
Podemos obtener el grafo de flujo a partir de un diagrama de flujo o a partir del código.
Ejemplo 1: Grafo de flujo a partir de un diagrama de flujo (Mayor)
Aquí tenemos el diagrama de flujo para un algoritmo que solicita dos números al usuario y
dice cual de ellos es mayor, o si son iguales.
Para obtener el grafo de
flujo se numera cada elemento
del diagrama (lectura, escritura,
condición, etc), incluido el
inicio (I) y el final (F).
Posteriormente, al dibujar el
grafo, agrupamos en un solo
nodo todos los elementos del
diagrama que siempre se
ejecutan juntos.
El número (7) corresponde a la primera condición: a>0. Si cuando el compilador evalúa esta
condición obtiene un valor falso, no evaluará la segunda porque independientemente de su valor, el
camino a seguir ya está determinado.
El número (8) corresponde a la segunda condición: b>0. Esta condición sólo será evaluada en
caso de que la primera tenga un valor de verdadero.
El grafo de flujo, que aparece en la siguiente
imagen, tiene:
➢ 5 nodos
➢ 6 aristas
➢ 2 nodos predicado (en amarillo)
➢ 3 regiones
COMPLEJIDAD CICLOMÁTICA
Mide la complejidad de un programa y coincide con el número de caminos distintos que
puede seguir el flujo. Contar el número de caminos puede ser complicado, sobre todo cuando
aumenta el tamaño y complejidad del grafo. Por eso nos ayuda saber que ese número coincide con:
➢ El número de caminos distintos.
➢ El número de regiones del grafo.
➢ El número de aristas, restando el número de nodos y sumando 2: (aristas-nodos+2)
➢ El número de nodos predicado más 1: (predicados + 1)
Una complejidad ciclomática por encima del valor 20 en un procedimiento entraña un riesgo
alto porque indica que se trata de un programa complejo. Si pasa de 50 hablamos de "muy alto
riesgo" o incluso, de programas no testeables. Para evitar estos valores podemos ayudarnos de la
programación modular, es decir, dividir el programa en varios módulos o subprogramas.
Ejemplo 1: Complejidad ciclomática del programa Mayor.
El grafo tiene 6 nodos, 7 aristas, 2 nodos predicado y 3 regiones. Si aplicamos las fórmulas
veremos que la complejidad ciclomática del ejemplo 1 es 3, es decir, hay 3 caminos distintos y son:
➢ camino 1: I, 1, 2, 3, 4, F
➢ camino 2: I, 1, 2, 3, 5, 6, F
➢ camino 3: I, 1, 2, 3, 5, 7, F
Ejemplo 2: Complejidad ciclomática del Sumador
Este grafo tiene 5 nodos, 6 aristas, 2 nodos predicado y 3 regiones. Aplicando cualquiera de
las fórmulas expuestas anteriormente concluimos que tiene 3 caminos distintos y por tanto,
tendremos que elaborar 3 casos de prueba. Los caminos son:
➢ camino 1: I, 1-7, 8, 9-10, 12, F
➢ camino 2: I, 1-7, 8, 11, 12, F
➢ camino 3: I, 1-7, 11, 12, F
CASOS DE PRUEBA
Conocidos los caminos hay que construir casos de prueba para cada camino, eligiendo datos
de entrada que provoquen la bifurcación de los nodos predicado hacia donde nosotros queremos.
Construimos una tabla con una fila para cada camino, describimos el caso de prueba,
normalmente con los valores de entrada y el resultado que sería correcto.
Ejemplo 1: Casos de prueba necesarios para chequear todos los caminos (Mayor)
Caso de prueba Valores entrada Resultado esperado
numero1=10
El usuario introduce el primer número mayor que el segundo. Camino 1 Mostrará el número 10
numero2=5
numero1=1
El usuario introduce el primer número menor que el segundo. Camino 2 Mostrará el número 5
numero2=5
numero1=8 Mostrará el mensaje "son
El usuario introduce el primer número igual que el segundo. Camino 3
numero2=8 iguales"
Ejemplo2: Casos de prueba necesarios para chequear todos los caminos del Sumador
Caso de prueba Valores entrada Resultado esperado
Mensaje: "El resultado es: 69" y
El usuario introduce dos valores positivos. Camino 1 a=24 y b=45
final.
El usuario introduce un valor positivo y un valor no positivo.
a=18 y b=-12 Mensaje: "No son positivos" y final.
Camino 2
El usuario introduce un primer no positivo.
a=-15 y b=4 Mensaje: "No son positivos" y final.
(el segundo valor es irrelevante). Camino 3
Ejemplo 3: Grafo de flujo a partir de un diagrama de flujo (Adivinador)
Aquí tenemos el diagrama de flujo para un algoritmo que genera un número aleatorio y pide
al usuario que lo adivine. El
usuario podrá seguir
intentándolo hasta acertar y
finalizar. Si no acierta, se le
informa si el número que tiene
que adivinar es mayor o menor
que el introducido.
Para obtener el grafo de
flujo se numera cada elemento
del diagrama (lectura, escritura,
condición, etc), incluido el
inicio (I) y el final (F).
Posteriormente, al dibujar el
grafo, agrupamos en un solo
nodo todos los elementos del
diagrama que siempre se
ejecutan juntos.
CAMINOS IMPOSIBLES
Si aplicamos las fórmulas veremos que la
complejidad ciclomática es 4, es decir, hay 4 caminos
distintos. Sin embargo, en este caso no es cierto porque algunos de los caminos resultantes no son
posibles. Por ejemplo, uno de los caminos que podríamos obtener de este grafo es: I, 1, 2, 3, 4, 5, 6,
8, 9, 10, 11, F. Si este camino pasa del nodo 6 al 8 es porque la condición intento > nPensado se
evalúa como cierta. Puesto que el valor de las variables intento y nPensado no se alteran en los
nodos 8 y 9, cuando llegamos al nodo 10, la condición intento = nPensado se tiene que evaluar
como falsa. Por tanto, desde el nodo 10 debe pasar al nodo 3 y no al nodo 11.
La consecuencia es que algunos caminos que resultan del grafo anterior son caminos
imposibles. Esta situación se produce cuando hay nodos predicado a los que llega más de una
flecha y tenemos que mejorar estos grafos indicando cuáles de las salidas son posibles para cada
entrada:
Observa las flechas rojas que hemos añadido
al grafo. El nodo 9,10 tiene tres entradas y dos
salidas, pero no todas son compatibles entre sí:
cuando el flujo llega desde el nodo 6 la única
dirección posible es hacia el nodo 11. Y cuando el
flujo llega desde el nodo 7 o desde el nodo 8 la
única salida posible es hacia el nodo 3.
Una vez descubierta esta situación
dibujaremos el verdadero grafo de flujo, en el que
repetiremos el nodo 9,10 tantas veces como sea
necesario (en este caso, dos veces) para que se
ajuste a la situación real.
Y la entrada completa del método, que incluye ambos datos, necesitará 15 casos de prueba
que son los que se obtienen de combinar las 5 clases de la edad con las 3 clases del género
(5x3=15):
Clases de equivalencia Valores elegidos Resultado esperado
NV1 con V6 edad = -7 y genero = ‘h’ Genera una excepción por edad
NV1 con V7 edad = -7 y genero = ‘M’ Genera una excepción por edad
NV1 con NV8 edad = -7 y genero = ‘a’ Genera una excepción por edad
V2 con V6 edad = 15 y genero = ‘H’ “menor”
V2 con V7 edad = 15 y genero = ‘m’ “menor”
V2 con NV8 edad = 15 y genero = ‘s’ “menor”
V3 con V6 edad = 50 y genero = ‘H’ “trabajador”
V3 con V7 edad = 50 y genero = ‘m’ “trabajadora”
V3 con NV8 edad = 50 y genero = ‘?’ Genera una excepción por genero
V4 con V6 edad = 100 y genero = ‘h’ “jubilado”
V4 con V7 edad = 100 y genero = ‘M’ “jubilada”
V4 con NV8 edad = 100 y genero = ‘9’ Genera una excepción por genero
NV5 con V6 edad = 200 y genero = ‘h’ Genera una excepción por edad
NV5 con V7 edad = 200 y genero = ‘M’ Genera una excepción por edad
NV5 con NV8 edad = 200 y genero = ‘o’ Genera una excepción por edad
Nuestras pruebas deben incluir al menos estos 15 casos de prueba porque las clases de
equivalencia nos dicen que son 15 situaciones claramente distintas. No obstante, se pueden crear
más (varios casos de prueba para cada clase de equivalencia).
Recuerda: el uso de clases de equivalencia es independiente del uso del conjunto básico de
caminos, de modo que a la tabla anterior habrá que añadir los casos de prueba que resulten del
conjunto de caminos.
NV1 con V7 edad = -1 y genero = ‘M’ Genera una excepción por edad
NV1 con NV8 edad = -7 y genero = ‘a’ Genera una excepción por edad
NV1 con NV8 edad = -1 y genero = ‘a’ Genera una excepción por edad
V2 con V6 edad = 15 y genero = ‘H’ “menor”
V2 con V6 edad = 0 y genero = ‘H’ “menor”
V2 con V6 edad = 17 y genero = ‘H’ “menor”
V2 con V7 edad = 15 y genero = ‘m’ “menor”
V2 con V7 edad = 0 y genero = ‘m’ “menor”
V2 con V7 edad = 17 y genero = ‘m’ “menor”
V2 con NV8 edad = 15 y genero = ‘s’ “menor”
V3 con V6 edad = 50 y genero = ‘H’ “trabajador”
V3 con V6 edad = 18 y genero = ‘H’ “trabajador”
V3 con V6 edad = 64 y genero = ‘H’ “trabajador”
V3 con V7 edad = 50 y genero = ‘m’ “trabajadora”
V3 con V7 edad = 18 y genero = ‘m’ “trabajadora”
V3 con V7 edad = 64 y genero = ‘m’ “trabajadora”
V3 con NV8 edad = 50 y genero = ‘?’ Genera una excepción por genero
V4 con V6 edad = 100 y genero = ‘h’ “jubilado”
V4 con V6 edad = 65 y genero = ‘h’ “jubilado”
V4 con V6 edad = 125 y genero = ‘h’ “jubilado”
V4 con V7 edad = 100 y genero = ‘M’ “jubilada”
V4 con V7 edad = 65 y genero = ‘M’ “jubilada”
V4 con V7 edad = 125 y genero = ‘M’ “jubilada”
V4 con NV8 edad = 100 y genero = ‘9’ Genera una excepción por genero
NV5 con V6 edad = 200 y genero = ‘h’ Genera una excepción por edad
NV5 con V6 edad = 126 y genero = ‘h’ Genera una excepción por edad
NV5 con V7 edad = 200 y genero = ‘M’ Genera una excepción por edad
NV5 con V7 edad = 126 y genero = ‘M’ Genera una excepción por edad
NV5 con NV8 edad = 200 y genero = ‘o’ Genera una excepción por edad
NV5 con NV8 edad = 126 y genero = ‘o’ Genera una excepción por edad
La tabla que hemos conseguido trata de escenificar la importancia de ocuparse de todas las
situaciones distintas a las que se pueda enfrentar el código. Probablemente sea exagerada la
cantidad de casos de prueba para un método tan simple y se podrían eliminar algunos de ellos, pero
procurando que siempre quede algún caso de prueba en cada valor límite y en cada combinación de
clases de equivalencia.
Recuerda: A esta tabla habría que añadir los casos de prueba que surjan del conjunto básico
de caminos, pero se pueden desechar aquellos caminos que ya son recorridos con los casos de
prueba ya contemplados.
Eclipse Netbeans
Ver el valor actual de una variable. pasando el ratón sobre ella
Paso a paso (Step Info). F5 F7
Hasta próximo breakpoint. F8 F5
Hasta la línea en la que está el cursor (Run to Line). CTRL-R F4
Paso a paso, sin entrar en métodos (Step Over). F6 MAY-F8
Hasta salir del método (Step Return). F7 CTRL-F7
Finalizar la ejecución en depuración (Terminate). CTRL-F2 MAY-F5
(o cuadro rojo) (o cuadro rojo)
Nota: Para ver un ejemplo de depuración de código Java con Eclipse puedes ver en Youtube
la píldora informática 150: https://www.youtube.com/watch?v=ymV7lUUHkUU
Otra de las acciones que permite el debugger es configurar los puntos de interrupción de
modo que estén activados/desactivados o que sean condicionales. Supongamos que tenemos un
bucle que hace 1000 iteraciones (desde i=0 hasta i=999) y queremos observar lo que ocurre dentro
del bucle en las dos últimas iteraciones. Establecemos un breakpoint dentro del bucle y el debugger
se detendrá en él las 1000 veces, de modo que tendremos que usar la opción "continuar hasta el
próximo breakpoint" 1000 veces.
Sin embargo podemos establecer ese breakpoint como condicional para que sólo esté activo si
se cumple la condición "i==998". De esta manera, la ejecución se detendrá antes de las dos últimas
iteraciones que es lo que necesitamos.
Para configurar los breakpoints:
Eclipse Netbeans
clic-dcho en el círculo del punto de
clic-dcho en el cuadrado del punto de
interrupción/propiedades del punto de
interrupción/breakpoint/properties.
interrupción
También podemos acceder a la configuración de También podemos acceder a la configuración de
los breakpoints en la ventana puntos de los breakpoints en la pestaña adecuada de la
interrupción (visibilizable en Ventana/Mostrar ventana inferior. (Visibilizable en
vista/Otras/Depurar) Windows/Debugging/Breakpoints)
Marcamos la casilla condicional y
Activamos la casilla de la condición y
seleccionamos "parar cuando true" o "parar
escribimos la condición con la que queremos
cuando cambie el valor". A continuación
que se active el punto de ruptura
escribimos la condición.
Nota: Para un ejemplo de breakpoints condicionales con Eclipse y algunas cosas más puedes
ver en Youtube la píldora informática 151: https://www.youtube.com/watch?v=qvNEQ2nAEVE
3.5 JUnit
JUnit es una herramienta que permite hacer pruebas unitarias automatizadas. Está integrada
tanto en Eclipse como en Netbeans por lo que no es necesario descargar ni instalar nada. Las
pruebas unitarias se aplican sobre una clase de forma aislada, aunque a veces es imposible por su
dependencia de otras clases.
Nota: La última versión de JUnit en el momento de escribir este documento es la versión 5,
pero es habitual hablar de JUnit Jupiter que es el subproyecto de JUnit 5 que nos interesa.
Tras seguir estos pasos se habrá creado una nueva clase que se usa exclusivamente para hacer
pruebas a la clase original. Por tanto la nueva clase no formará parte del proyecto final.
Si estás trabajando con Eclipse, la nueva clase se genera en el mismo paquete que la original.
Si estás trabajando con Netbeans, la nueva clase se genera en una estructura paralela que, en lugar
de colgar de la carpeta src, cuelga de la carpeta test. En realidad, la ubicación de la nueva clase no
es importante y, seguramente, la podrás cambiar de sitio. Recuerda que las clases de prueba no
formarán parte del proyecto final.
En el ejemplo siguiente, para probar la clase Enteros creamos una nueva clase (EnterosTest)
en la que aparece el esqueleto de los métodos que probarán nuestros métodos originales. Si en la
clase Enteros hay un método que se llama factorial(), en la clase EnterosTest habrá un método que
se llama testFactorial().
Ejemplo: Clase generada (en Eclipse) con JUnit Jupiter para testear la clase Enteros.java.
Nota: La clase Enteros la tienes disponible entre los ejemplos de código del tema. Esa clase
incluye 11 métodos que podremos usar como ejemplos. Al crear la clase de pruebas podemos
decidir cuáles de los métodos queremos probar. El código siguiente corresponde a una situación en
la que el usuario ha seleccionado solamente tres de los once métodos.
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class EnterosTest {
@Test
final void testFactorial() {
fail("No implementado aun");
}
@Test
final void testEsPrimo() {
fail("No implementado aun");
}
@Test
final void testDivide() {
fail("No implementado aun");
}
}
El resultado que obtendremos será una clase con tantos métodos como hayamos seleccionado
y con el mismo nombre, pero precedidos de la palabra "test". Serán métodos que no devuelven
ningún valor (void) y no reciben argumentos. Delante del método aparece la anotación @Test que
indica al compilador que es un método de prueba.
Nota: En anteriores versiones de JUnit las clases y métodos de pruebas tenían que ser
públicos. En la versión 5 no es necesario, lo cual supone una ventaja, porque no podrán ser
invocados desde fuera de JUnit 5.
Cada uno de los métodos tendrá, originalmente, una llamada al método fail() con un mensaje
que indica que no se ha implementado el método. Esta llamada hará que el test genere un fallo con
ese mensaje. Habrá que sustituirla por el código correspondiente a nuestros casos de prueba.
Nota: Si usas Netbeans el código generado automáticamente será ligeramente distinto, pero
en realidad no tiene importancia porque el cuerpo de los métodos tendrás que escribirlo tú, tanto en
un IDE como en el otro.
Que viene a decir algo así como "comprueba que al llamar al método esPrimo() con el valor 4
devolverá false y que al llamar al método esPrimo() con el valor 7 devolverá true".
Por otra parte, hemos visto que hay dos métodos con el mismo nombre (assertEquals):
➢ El primero de ellos (dos argumentos) nos permite comprobar el resultado de un
método que podemos expresar de forma exacta. Al dividir 3 entre 2 se debe obtener
1'5 que es un valor que podemos expresar de forma exacta:
assertEquals (1.5, Enteros.divide(3,2));
@Test
final void testFactorial() {
assertEquals(120,Enteros.factorial(4));
}
@Test
final void testDivisible() {
assertTrue(Enteros.divisible(15, 0));
}
@Test
final void testEsPrimo() {
assertFalse(Enteros.esPrimo(4));
assertTrue(Enteros.esPrimo(7), "Fallo en esPrimo(7)");
}
@Test
final void testPrimosHasta() {
int[] esperado={2,3,5,7};
assertArrayEquals(esperado, Enteros.primosHasta(10));
}
}
En Eclipse, asegúrate que no están marcados los botones “mostrar solo anomalías” ( )y
tests saltados “mostrar skipped test”. ( )
Hay que tener en cuenta que JUnit solo informa de una anomalía por cada método de prueba.
Si en un mismo método de prueba hay dos o más casos de prueba que presentan anomalías (fallo o
error) nos informa del primero de ellos.
En Netbeans, bloquea los botones de lo que quieres mostrar: éxitos, fallos, errores y abortos.
En la clase Enteros tenemos un método (divide()) que genera una excepción cuando se le pasa
un divisor igual a cero. Entre los casos de prueba deberá haber uno que contemple esa situación:
"cuando se reciba un divisor igual a cero se debe generar la excepción ArithmeticException".
dividendo divisor resultado esperado
7 0 ArithmeticException
A continuación mostramos el que podría ser el método de prueba resaltando en amarillo el
caso de prueba que genera la excepción:
@Test
final void testDivide() {
assertEquals (1.5, Enteros.divide(3,2));
assertEquals (0.33, Enteros.divide(1,3), 0.01);
assertThrows (ArithmeticException.class, ()->Enteros.divide(7,0));
}
En este método se pueden generar dos excepciones distintas. Fíjate en la cabecera del método:
public static String clasifica(int edad, char genero) throws
EdadErroneaException, GeneroErroneoException
Ya habíamos calculado los casos de prueba que necesita este método. Puedes verlos en la
página 16. Esos 33 casos de prueba los podemos distribuir en un solo método o en varios, como
queramos. En este caso hemos creado un solo método de prueba con los 33 casos de prueba:
public class PoblacionTest1 {
@Test
public void testClasifica() throws EdadErroneaException,
GeneroErroneoException {
assertEquals("menor", Poblacion.clasifica(15,'H'));
assertEquals("menor", Poblacion.clasifica(0,'H'));
assertEquals("menor", Poblacion.clasifica(17,'H'));
assertEquals("menor", Poblacion.clasifica(15,'m'));
assertEquals("menor", Poblacion.clasifica(0,'m'));
assertEquals("menor", Poblacion.clasifica(17,'m'));
assertEquals("menor", Poblacion.clasifica(15,'s'));
assertEquals("trabajador", Poblacion.clasifica(50,'H'));
assertEquals("trabajador", Poblacion.clasifica(18,'H'));
assertEquals("trabajador", Poblacion.clasifica(64,'H'));
assertEquals("trabajadora", Poblacion.clasifica(50,'m'));
assertEquals("trabajadora", Poblacion.clasifica(18,'m'));
assertEquals("trabajadora", Poblacion.clasifica(64,'m'));
assertEquals("jubilado", Poblacion.clasifica(100,'h'));
assertEquals("jubilado", Poblacion.clasifica(65,'h'));
assertEquals("jubilado", Poblacion.clasifica(125,'h'));
assertEquals("jubilada", Poblacion.clasifica(100,'M'));
assertEquals("jubilada", Poblacion.clasifica(65,'M'));
assertEquals("jubilada", Poblacion.clasifica(125,'M'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(-7, 'h'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(-1, 'h'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(-7, 'M'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(-1, 'M'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(-7, 'a'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(-1, 'a'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(200, 'h'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(126, 'h'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(200, 'M'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(126, 'M'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(200, 'o'));
assertThrows(EdadErroneaException.class,()->Poblacion.clasifica(126, 'o'));
assertThrows(GeneroErroneoException.class,()->Poblacion.clasifica(50, '?'));
assertThrows(GeneroErroneoException.class,()->Poblacion.clasifica(100, '9'));
}
}
Muy importante: Al generar los métodos de prueba, como ya sabes, se hace una llamada al
método que estamos probando. El IDE alertará que esa llamada puede generar excepciones y nos
ofrecerá como siempre dos alternativas: rodear con try/catch o propagar la excepción. En los
métodos de prueba siempre se elegirá propagar la excepción.
@VALUESOURCE
Con @ValueSource la fuente de los argumentos es una lista de valores individuales.
@ValueSource es una de las fuentes más simples. Permite especificar un array lineal de valores
literales y solo vale para proporcionar un argumento simple en la invocación del test parametrizado.
Ejemplo: @ValueSource. Valores que devuelven TRUE en el método esPrimo()
@ParameterizedTest
@DisplayName("Los que sí son primos")
@ValueSource(ints = {7, 17, 19, 23})
final void testEsPrimo2(int candidato) {
assertTrue(Enteros.esPrimo(candidato));
}
Este método se ejecutará 4 veces (la fuente de valores tiene 4 datos) y en cada invocación, el
argumento candidato tomará sucesivamente los valores del array.
Nota: La palabra ints es un atributo de la anotación @ValueSource que indica que los datos
proporcionados son un array de enteros. Otros atributos que podemos usar aquí son floats, chars,
strings, doubles, etc. En https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/
jupiter/params/provider/ValueSource.html tienes la lista completa.
Nota: Puedes ver los ejemplos de @ValueSource en la clase
EnterosTestParametrizados_MUESTRARIO.java en el código que se proporciona con este tema.
@CSVSOURCE
Si usamos @CsvSource la fuente que proporciona los argumentos es una lista de conjuntos de
valores. Cada conjunto de valores es un String con los valores separados por comas (CSV-Comma
Separated Values). JUnit hará implícitamente el casting de cada valor a su tipo.
Ejemplo: @CsvSource. Conjuntos de valores de entrada y salida en el método clasifica()
@ParameterizedTest
@DisplayName("Pruebas clasifica SIN GENERAR EXCEPCIONES")
@CsvSource({"menor, 15, H",
"trabajador, 18, H",
"trabajador, 64, H",
"jubilada, 65, M",})
final void testClasifica(String salida, int edad, char genero) throws
EdadErroneaException, GeneroErroneoException {
assertEquals(salida, Poblacion.clasifica(edad,genero));
}
En este ejemplo vemos la ejecución de algunos casos de prueba que no deben generar
excepciones. La fuente de valores CSV es un array de Strings. Cada String es un "conjunto de
valores separados por comas". Como puedes ver en cada uno de esos conjuntos tenemos un String,
un int y un char en ese orden. Observa que no se usan comillas para los String ni los char. JUnit
hace el casting de forma implícita aunque debes preocuparte que estén en el orden adecuado
coincidiendo con el orden que tienen los argumentos en el método testClasifica().
Nota: Puedes ver ejemplos de @CsvSource en la clase
PoblacionTestParametrizados_CSV.java. Ambas clases se proporcionan en el código de este tema.
@METHODSOURCE
Si usamos @MethodSource la fuente que proporciona los datos es un método que genera una
lista de conjuntos de argumentos. Cada conjunto de argumentos se usará en una invocación al
método de prueba.
Así pues por cada método de prueba parametrizado con @MethodSource habrá que escribir
un método generador de datos que genere un flujo de argumentos para el método de prueba.
Cada método generador de datos debe generar un flujo de argumentos, y cada conjunto de
argumentos de ese flujo será proporcionado al método de prueba parametrizado para invocaciones
individuales. En general esto significa que el método generador tendría que devolver el tipo
Stream<Arguments>, pero no necesariamente, porque en este contexto, un flujo es cualquier cosa
que JUnit pueda convertir en Stream, tales como los propios Stream o colecciones, arrays, etc.
Cada uno de los elementos de ese flujo serán los argumentos de una invocación individual del
método de prueba. Esos argumentos pueden ser una instancia de Arguments, un array de objetos o
un valor simple si el método @ParameterizedTest acepta un argumento simple.
En la siguiente tabla hay ejemplos en los que se muestra que tipo de dato debería devolver un
método generador de datos, según la cabecera del método de prueba parametrizado, es decir, según
los argumentos que recibe.
método @ParameterizedTest método generador de datos
1 void test(int) static int[] factory()
2 void test(int) static IntStream factory()
3 void test(String) static String[] factory()
4 void test(String) static List<String> factory()
5 void test(String) static Stream<String> factory()
6 void test(String, String) static String[][] factory()
7 void test(String, int) static Object[][] factory()
8 void test(String, int) static Stream<Object[]> factory()
9 void test(String, int) static Stream<Arguments> factory()
10 void test(int[]) static int[][] factory()
11 void test(int[]) static Stream<int[]> factory()
12 void test(int[][]) static Stream<int[][]> factory()
13 void test(Object[][]) static Stream<Object[][]> factory()
Si el flujo proporciona arrays unidimensionales de objetos, cada uno de los elementos del
array será uno de los argumentos del método de prueba. Fíjate en la fila 8 de la tabla. El método de
prueba parametrizado está esperando un String y un int, y el método generador de datos
proporciona un flujo de arrays de objetos, donde cada array tendrá exactamente dos objetos: un
String y un int.
Sin embargo, si el flujo proporciona arrays multidimensionales, cada array multidimensional
en bloque será considerado como un argumento individual. Fíjate en las filas 12 y 13 de la tabla.
Los métodos de prueba parametrizados están esperando un argumento individual del tipo int[][] y
Object[][] respectivamente.
Ejemplo: Método de prueba parametrizado con @MethodSource para el método esPrimo()
@ParameterizedTest
@DisplayName("Los que sí son primos -method-")
@MethodSource("datosEsPrimo4")
final void testEsPrimo4(int candidato) {
assertTrue(Enteros.esPrimo(candidato));
}
El método de prueba recibirá los datos de un método llamado datosEsPrimo4() tal y como se
indica en la línea señalada con fondo amarillo.
A continuación se ha definido el método generador que devolverá una lista de 4 enteros. Cada
uno de esos enteros se usará como argumento (candidato) en una invocación del método de prueba.
Nota: Este ejemplo se corresponde con la fila 1 de la tabla.
Ejemplo: Método de prueba parametrizado con @MethodSource para el método alreves()
@ParameterizedTest
@DisplayName("Pruebas alreves() -method-")
@MethodSource("datosAlreves3")
final void testAlreves3(String entrada, String salida) {
assertEquals(salida, Enteros.alreves(entrada));
}
El método de prueba recibirá los datos de un método llamado datosAlreves3() tal y como se
indica en la línea señalada con fondo amarillo.
A continuación se ha definido el método generador que devolverá una lista de 3 arrays de
String. Cada uno de esos arrays es un conjunto de argumentos (entrada, salida) para una
invocación del método de prueba.
Nota: Este ejemplo se corresponde con la fila 6 de la tabla.
El método de prueba recibirá los datos de un método llamado datosClasifica() tal y como se
indica en la línea señalada con fondo amarillo.
A continuación se ha definido el método generador que devolverá una lista de 3 arrays de
Object. Cada uno de esos arrays es un conjunto de argumentos (salida, edad, genero) para una
invocación del método de prueba. Observa que cada uno de los argumentos es de un tipo distinto:
String, int, char. Por eso el tipo tiene que ser Object, que es el más genérico y los admite a los tres.
Nota: Este ejemplo se corresponde con la fila 7 de la tabla, aunque en este ejemplo hay un
argumento más.