DEV Community

Cover image for JUnit Cómo parametrizar un test
Ciro
Ciro

Posted on

JUnit Cómo parametrizar un test

JUnit cuenta con una serie de funcionalidades orientadas a ejecutar tests en nuestra aplicación.
Una de estas funcionalidades son los tests parametrizados, que nos permiten poder declarar los valores que necesitemos fuera del propio test.

Para entender su funcionamiento supongamos que nuestra aplicación cuenta con un validador de códigos bancarios IBAN, que comprueba si un valor determinado cumple el patrón esperado en dichos códigos.

Los códigos IBAN utilizados en esta publicación han sido generados mediante la web http://randomiban.com/?country=Spain

class IbanValidator {
    // Regex pattern that an Iban must match
    static final String PATTERN = "^ES[0-9]{2}[0-9]{20}$";

    boolean isValid(String iban) {
        return iban.matches(PATTERN);
    }    
}
Enter fullscreen mode Exit fullscreen mode

Ahora supongamos que tenemos un test sobre esta clase con la siguiente forma:

@Test
@DisplayName(value = "Check validity of IBAN")
void check_validity_iban() {
    IbanValidator validator = new IbanValidator();
    assertTrue(validator.isValid("ES7921000813610123456789"));
}

// Test passed: 1 of 1 ✅
Enter fullscreen mode Exit fullscreen mode

Como podemos ver, en este ejemplo estamos comprobando únicamente un valor concreto para el código IBAN, por lo que el test estaría sesgado.
Podríamos enfrentarnos a un falso positivo, ya que si desconocemos la implementación del método isValid() podría ser que siempre devuelva true.

Para resolver este problema, sería mejor probar la validación con diferentes valores de IBAN. Nuestro primer instinto podría ser copiar el test N veces, tantas como códigos IBAN diferentes queramos probar, pero JUnit nos facilita este trabajo mediante el uso de la anotación @ParameterizedTest.

@ParameterizedTest

Para indicar el origen de los parámetros de un test parametrizado, debemos hacer uso de otras anotaciones como:

  1. @ValueSource: permite indicar los valores directamente en la anotación.
  2. @MethodSource: permite hacer referencia a un método proveedor que contiene los parámetros.

@ValueSource

Utilizando esta anotación, podemos declarar los valores del parámetro de nuestro test:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

@ParameterizedTest  // <- Utilizamos la anotación @ParameterizedTest en lugar de @Test
@ValueSource(strings = {"ES7921000813610123456789", "ES1600755981885574867892"}) // <- Indicamos en la anotación @ValueSource el tipo de parámetro (en este caso String) seguido de los valores que queremos probar
@DisplayName(value = "Check validity of IBAN")
void check_validity_iban(String iban) { // <- Indicamos en el test el tipo y nombre del nuestro parámetro
    IbanValidator validator = new IbanValidator();
    assertTrue(validator.isValid(iban));
}

// Test passed: 2 of 2 ✅
Enter fullscreen mode Exit fullscreen mode

Una de las limitaciones que tiene @ValueSource es que únicamente podemos hacer uso de un parámetro para el test, y en ocasiones esto puede ser insuficiente.
Para resolver este problema debemos hacer uso de la anotación alternativa @MethodSource.

@MethodSource

Aplicando esta anotación al mismo ejemplo, el test quedaría de la siguiente forma:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

private static Stream<Arguments> testIbanValidityParams() { // <- Creamos un método proveedor donde indicamos 
    return Stream.of(
        Arguments.of("ES7921000813610123456789"),
        Arguments.of("ES1600755981885574867892")
    )
}

@ParameterizedTest
@MethodSource(value = "testIbanValidityParams") // <- Indicamos en la anotación @MethodSource el nombre del método que contiene los parámetros
@DisplayName(value = "Check validity of IBAN")
void check_validity_iban(String iban) {
    IbanValidator validator = new IbanValidator();
    assertTrue(validator.isValid(iban));
}

// Test passed: 2 of 2 ✅
Enter fullscreen mode Exit fullscreen mode

Como podemos ver, lo único que necesitamos es crear un método estático que devuelva un Stream<Arguments>, y estos serán utilizados por el test parametrizado.
Algunas de las ventajas de esta anotación frente a @ValueSource son:

  1. Al extraer los parámetros a un método, podemos documentar mejor el test, ya que podemos comentar en el método proveedor información sobre cada parámetro.
  2. El método Arguments.of() espera que indiquemos 1 o N parámetros, por lo que podemos aumentar los casos de uso de nuestro test realizando una simple modificación:
/**
 * Parameters:
 * 1. String - An example of an IBAN code to be validated.
 * 2. boolean - The expected validation
 */
private static Stream<Arguments> testIbanValidityParams() {
    return Stream.of(
        Arguments.of("ES7921000813610123456789", true), // <- Creamos un nuevo parámetro booleano con la respuesta esperada
        Arguments.of("ES1600755981885574867892", true),
        Arguments.of("ESX921000813610123456789", false),
        Arguments.of("E81600755981885574867892", false)
    )
}

@ParameterizedTest
@MethodSource(value = "testIbanValidityParams")
@DisplayName(value = "Check validity of IBAN")
void check_validity_iban(String iban, boolean expected) {   // <- Indicamos el nuevo parámetro 'expected'
    IbanValidator validator = new IbanValidator();
    assertEquals(actual, expected); // <- Modificamos la afirmación de 'assertTrue' a 'assertEquals'
}

// Test passed: 4 of 4 ✅
Enter fullscreen mode Exit fullscreen mode

En esta última versión del test se puede apreciar lo fácil que sería incluir un nuevo caso de uso para la validación, así como nuevos parámetros para comprobar en el test.

Top comments (0)