Teses unitarios parametrizados con JUnit

Escrito por el .
java planeta-codigo programacion
Comentarios

Hay múltiples lenguajes y librerías donde cada una publica nuevas versiones. Una vez elegida una esa decisión no tiene que ser para siempre si las circunstancias de un proyecto cambian o una nueva versión incorpora las funcionalidades por las que se eligió otra. Si se reconsideran de nuevo el conjunto de todos los parámetros la decisión puede ser distinta. Esto me ha ocurrido al evaluar de nuevo JUnit comparándolo con Spock, teniendo en cuenta que en JUnit 5 han incorporado los teses parametrizados y el lenguaje que utiliza cada una de ellas.

JUnit
Java

En mis preferencias de herramientas que elegiría para un proyecto basado en la plataforma Java estaba Spock, por la legibilidad de los teses con su lenguaje específico de dominio o DSL con sus diferentes secciones given, when, then. Otro motivo era la posibilidad de ejercitar un mismo test pero con diferentes parámetros para ejecutar todas las condiciones del sujeto bajo prueba con la sección where y las posibilidades de mocking incorporadas. Pero Spock usa el lenguaje Groovy. Es menos verboso, es dinámico pero que no posee igual la asistencia de código de los IDEs y por su naturaleza dinámica con posibilidad de errores de compilación no detectados hasta en tiempo de ejecución. En mis preferencias está el lenguaje Java así que he revisado si estás características de Spock son ofrecidas por JUnit desde la última vez que lo use.

El primer motivo de usar Spock sobre la legibilidad del test se puede suplir añadiendo un comentario de línea con la sección. El segundo motivo es que en JUnit también se pueden crear teses parametrizados con varios casos de prueba. Para los teses parametrizados se puede usar la anotación @ParameterizedTest con una serie de valores que en el test se reciben como un parámetro.

Aqui se compara el mismo test usando Spock y luego JUnit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import spock.lang.Specification

class StringLengthCalculator {
    int length(String string) {
        return string.length()
    }
}

class StringLengthCalculatorSpock extends Specification {
    def "calculate string length"() {
        given:
        def calculator = new StringLengthCalculator()

        expect:
        def result = calculator.length(a)

        then:
        expected == result

        where:
        a         | expected
        ""        | 0
        "java"    | 4
        "groovy"  | 5
        "go"      | 2
    }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.provider.Arguments;

public class StringLengthCalculator {
    public int length(String string) {
        return string.length()
    }
}

public class StringLengthCalculatorTest {

    @ParameterizedTest
    @DisplayName("calculate string length")
    @ValueSource(strings = { "", "java", "groovy", "go" })
    void lengthOfStrings(String a) throws Exception {
        // given
        StringLengthCalculator calculator = new StringLengthCalculator();

        // expect
        int result = calculator.length(a);

        // then
        Assertions.assertEquals(expected, result);
    }
}

O si la parametrización es más compleja usando un método que devuelve una lista de parámetros en Junit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import spock.lang.Specification

class Calculator {
    int add(int a, int b) {
        return a + b
    }
}

class CalculatorSpock extends Specification {
    def "calculate sum"() {
        given:
        def calculator = new Calculator()

        expect:
        def result = calculator.add(a, b)

        then:
        expected == result

        where:
        a |  b | expected
        1 |  3 | 4
        2 | -1 | 1
        0 |  6 | 6
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

public class CalculatorTest {

    @ParameterizedTest
    @DisplayName("calculate sum")
    void lengthOfStrings(int a, int b, int expected) throws Exception {
        // given
        Calculator calculator = new Calculator();

        // when
        int result = calculator.add(a, b);

        // then
        Assertions.assertEquals(expected, result);
    }

    static Stream<Arguments> lengthOfStrings() {
        return Stream.of(
            Arguments.arguments(1, 3, 4),
            Arguments.arguments(2, -1, 1),
            Arguments.arguments(0, 6, 6),
        );
    }
}

Con estas posibilidades de JUnit y para hacer mocking con Mockito realmente los dos motivos que tenía para usar Spock no son imprescindibles además de disponer de un lenguaje con buena asistencia de código en los IDEs. También para los teses igualmente se aplican las 10 razones que tengo para seguir usando Java.