Skip to content

Un Ejemplo Práctico de Step Definitions de Cucumber en Java

Usamos un proyecto real como ejemplo práctico de cómo implementar step definitions con Cucumber en Java, y las mejores prácticas para mantener el estado entre pasos.

Esta tercera sección de la guía se centra en las clases Java de Step Definition. Usamos el proyecto de ejemplo real, y mapeamos las sentencias Gherkin a métodos Java con los llamados archivos Step Definition. Aprenderás sobre los Parameter Types integrados de Cucumber y crearás tu propio tipo personalizado. Además, pondremos en práctica las mejores prácticas para mantener el estado en una capa de abstracción separada.

Mantener el estado entre pasos de Cucumber

Al escribir tests de Cucumber en Java, es común usar la clase Step Definition para mantener el estado dentro de los múltiples métodos que participan en la ejecución. En nuestro caso, usemos el escenario siguiente como ejemplo.

  Scenario: Users solve challenges, they get feedback and their stats.
    Given a new user John
    And he requests a new challenge
    When he sends the correct challenge solution
    Then his stats include 1 correct attempt

Repositorio GitHub: Todo el código en este post está disponible en GitHub: Book - Cucumber Tests. Si lo encuentras útil, por favor dale una estrella!

En este caso de prueba, introducimos el alias del usuario en la primera sentencia Given. Más tarde, nos referimos al usuario con un pronombre. Esto implica que tenemos que guardar el alias entre los pasos, porque lo necesitaremos dentro del alcance, por ejemplo, del método Java que mapea la sentencia “his stats include 1 correct attempt”. Mira el código a continuación.

public class ChallengeStepDefinitions {

    private String userAlias;
    private ApiClient apiClient;

    // constructor

    @Given("a new user {word}")
    public void aNewUser(String user) {
        this.userAlias = user;
    }

    // ... (other methods)

    @Then("her/his stats include {int} {correct} attempt(s)")
    public void statsIncludeAttempts(int attemptNumber, boolean correct) {
        var stats = this.apiClient.getStats(this.userAlias);
        // verify that the stats contain the expected number of (in)correct attempts
    }
}

Sin entrar en detalles de las expresiones de Cucumber (lo haremos más tarde), puedes ver en este bloque de código cómo usamos el primer paso para almacenar el alias del usuario en una variable de instancia, a nivel de clase. El paso anotado con @Then asume que el paso anterior ha sido ejecutado antes, y obtiene el valor de la variable userAlias de la instancia. Este enfoque funciona porque Cucumber usa las mismas instancias de Step Definition para el escenario completo.

Nota: No puedes compartir estado entre escenarios porque Cucumber instancia nuevos objetos de step definition. Esto es bueno. Compartir estado entre casos de prueba es una mala práctica ya que los haces depender unos de otros. También podría llevar a situaciones no deseadas cuando, por ejemplo, ejecutas tus escenarios de prueba en paralelo. Si realmente lo necesitas, considera usar Dependency Injection.

El enfoque mostrado en el fragmento de código anterior es válido, pero no escala muy bien. Imagina que quieres probar características que usan múltiples actores, cada uno con un estado diferente. Como ejemplo, para probar la característica del Leaderboard, queremos enviar múltiples desafíos desde múltiples usuarios. En principio, eso no es un problema ya que podríamos usar un map de Java para mantener el estado de cada alias de usuario (claves) a sus correspondientes estadísticas (valores). Pero, ¿qué pasa si necesitamos almacenar también el último desafío que cada usuario ha enviado? Hmm, quizás necesitemos otro map. Y, ¿qué pasa si necesitamos establecer un estado similar con múltiples desafíos y usuarios en una clase Step Definition diferente (que creamos por separado para hacer nuestro proyecto de pruebas modular y seguir un enfoque de dominio)? En ese caso, podríamos vernos tentados a copiar todos estos maps allí también.

En lugar de replicar las estructuras de estado en cada clase de step definition, podemos introducir una capa de abstracción que modele las posibles interacciones con tu sistema y su estado, y reutilizarlas donde se necesiten. Eso es lo que haremos en la siguiente sección.

Simular el comportamiento del usuario en Cucumber

Podemos crear dos clases diferentes para modelar las interacciones con ambos dominios en nuestro sistema: Challenges, y Gamification. Estas clases actor contendrán métodos que simulan interacciones de dominio, y también mantienen el último estado para que podamos verificar sus valores en las aserciones de prueba.

La clase Challenge

El proceso de pedir un nuevo desafío de multiplicación y enviar un intento de resolverlo será capturado por la clase Challenge. Mira el código a continuación. Lee las notas siguientes para una mejor comprensión de lo que hace esta clase.

package microservices.book.cucumber.actors;

import java.util.List;
import java.util.UUID;

import microservices.book.cucumber.api.APIClient;
import microservices.book.cucumber.api.dtos.challenge.AttemptRequestDTO;
import microservices.book.cucumber.api.dtos.challenge.AttemptResponseDTO;
import microservices.book.cucumber.api.dtos.challenge.ChallengeDTO;

import static org.assertj.core.api.Assertions.*;

public class Challenge {

    private final String userName;
    private final String originalName;
    private final APIClient apiClient;
    private long userId;
    private ChallengeDTO currentChallenge;

    public Challenge(String userName) {
        this.userName = userName + "-" + UUID.randomUUID().toString();
        this.originalName = userName;
        this.apiClient = new APIClient();
    }

    public void askForChallenge() throws Exception {
        var challenge = apiClient.getChallenge();
        assertThat(challenge.statusCode()).isEqualTo(200);
        this.currentChallenge = apiClient.getChallenge().body();
    }

    public void solveChallenge(boolean correct) throws Exception {
        assertThat(this.currentChallenge)
                .as("You have to get a challenge first").isNotNull();
        var attemptResponse = apiClient.sendAttempt(
                AttemptRequestDTO.solve(this.currentChallenge,
                        correct, this.userName)
        );
        assertThat(attemptResponse.statusCode()).isEqualTo(200);
        this.userId = attemptResponse.body().getUser().getId();
    }

    public List<AttemptResponseDTO> retrieveStats() throws Exception {
        var stats = apiClient.getStats(this.userName);
        assertThat(stats.statusCode()).isEqualTo(200);
        return stats.body();
    }

    public ChallengeDTO getCurrentChallenge() {
        return currentChallenge;
    }

    public String getOriginalName() {
        return originalName;
    }

    public long getUserId() {
        return userId;
    }
}

Esta clase encapsula un objeto APIClient, así que no necesitamos hacer llamadas de red desde nuestros archivos de step definition sino solo llamar a los métodos del actor:

  • askForChallenge obtiene un nuevo desafío de multiplicación y lo almacena en una variable de instancia como el estado actual. También hay un getter para esta propiedad (getCurrentChallenge), así que podemos usarlo desde los scripts de prueba.
  • solveChallenge es un método conveniente para enviar rápidamente un intento que puede ser correcto o incorrecto. Nota que la lógica para enviar el resultado correspondiente para un desafío ya estaba incluida dentro de la clase AttemptRequestDTO.
  • retrieveStats contacta al servidor para obtener las estadísticas actuales del usuario y las devuelve al llamador.

Adicionalmente, esta clase también abstrae la aleatorización del alias de usuario. En pruebas end-to-end, deberíamos evitar reutilizar los mismos actores (por ejemplo, usuarios) porque probablemente mantenemos su estado en el lado del backend (por ejemplo, una base de datos). Si usáramos el mismo alias de usuario en todos nuestros casos de prueba, necesitaríamos dar acceso desde el backend al cliente (pruebas de cucumber) para eliminar todos los datos anteriores de ese usuario, para asegurarnos de que ejecutamos las pruebas con un estado limpio. Para evitar esto, puedes simplemente crear usuarios únicos todo el tiempo. De esta manera, puedes simplemente limpiar las bases de datos de prueba completas cuando quieras hacer la limpieza. Para abstraer la aleatorización y pretender que usamos el alias pasado a la clase Challenge, mantenemos tanto el nombre de usuario original (originalName) como la referencia aleatoria (userName).

Dado que nuestra clase actor trabaja con requests y responses HTTP, podemos incluir aserciones para verificar si el código de estado es OK antes de desempaquetar la respuesta como un objeto. En el código anterior puedes ver cómo incluimos estas aserciones en cada método que llama a las REST APIs. Esta es una buena práctica porque en caso de que el servidor falle, obtendremos un mensaje más amigable que si lo dejamos fallar con un NPE al llamar body(), por ejemplo.

La clase Leaderboard

El segundo actor en nuestras pruebas es la clase Leaderboard. Mira el código a continuación y la descripción correspondiente.

package microservices.book.cucumber.actors;

import java.util.List;
import java.util.Optional;

import microservices.book.cucumber.api.APIClient;
import microservices.book.cucumber.api.dtos.leaderboard.LeaderboardRowDTO;

import static org.assertj.core.api.Assertions.*;

public class Leaderboard {

    private final APIClient apiClient;
    private List<LeaderboardRowDTO> leaderboard;

    public Leaderboard() {
        apiClient = new APIClient();
    }

    public Leaderboard update() throws Exception {
        var leaderboard = apiClient.getLeaderboard();
        assertThat(leaderboard.statusCode()).isEqualTo(200);
        this.leaderboard = leaderboard.body();
        return this;
    }

    public int whatPosition(long userId) {
        for (int i = 0; i < leaderboard.size(); i++) {
            if (leaderboard.get(i).getUserId() == userId) {
                return i + 1;
            }
        }
        return -1;
    }

    public Optional<LeaderboardRowDTO> getByUserId(long userId) {
        return leaderboard.stream()
                .filter(row -> row.getUserId() == userId).findAny();
    }
}

Esta clase también usa un APIClient para recuperar el leaderboard y almacenarlo localmente. Llamamos al método update(). Nota que el leaderboard es el mismo para todos, así que no depende del usuario.

Agregamos dos métodos útiles que, basados en un identificador de usuario, recuperan la posición de ranking (whatPosition) o la fila del leaderboard (getByUserId) respectivamente. Los usaremos en nuestras pruebas.

Ahora que completamos nuestra Capa de Abstracción de Actor, podemos mantener nuestras step definitions simples. Veamos cómo.

Step definitions usando Cucumber JVM

Por un lado, tenemos los archivos Gherkin definiendo las características. Por otro lado, ya implementamos el API Client y lo encapsulamos en dos clases Actor para simular el comportamiento del Usuario y la interacción con el leaderboard. Solo nos faltan los mapeos entre los scripts de características y sus correspondientes Java Step Definitions.

En la primera parte de esta guía, introdujimos Gherkin, y tuvimos un vistazo rápido a cómo las Expresiones de Cucumber mapean cada paso de escenario a métodos Java en las clases Step Definition. La Parte 2 definió las características Gherkin de nuestro caso de uso práctico. Es hora de poner en práctica los conceptos sobre mapear pasos Gherkin a métodos Java usando expresiones de cucumber.

Primero, crearemos una nueva clase ChallengeStepDefinitions. En esta clase, incluiremos el mapeo para todos los pasos requeridos para interactuar con el dominio Challenge. Mira el código a continuación. Describiremos los conceptos principales introducidos en este archivo pronto.

package microservices.book.cucumber.steps;

import io.cucumber.java.ParameterType;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;

import microservices.book.cucumber.actors.Challenge;

import static org.assertj.core.api.Assertions.*;

public class ChallengeStepDefinitions {

    private Challenge challengeActor;

    @Given("a new user {word}")
    public void aNewUser(String user) {
        this.challengeActor = new Challenge(user);
    }

    @When("(s)he requests a new challenge")
    public void userRequestsANewChallenge() throws Exception {
        this.challengeActor.askForChallenge();
    }

    @Then("(s)he gets a mid-complexity multiplication to solve")
    public void getsAMidComplexityMultiplicationToSolve() {
        assertThat(this.challengeActor.getCurrentChallenge().getFactorA())
                .isBetween(9, 100);
        assertThat(this.challengeActor.getCurrentChallenge().getFactorB())
                .isBetween(9, 100);
    }

    @When("(s)he sends the {correct} challenge solution")
    public void userSendsTheCorrectChallengeSolution(boolean correct) throws Exception {
        this.challengeActor.solveChallenge(correct);
    }

    @Then("her/his stats include {int} {correct} attempt(s)")
    public void statsIncludeAttempts(int attemptNumber, boolean correct) throws Exception {
        var stats = this.challengeActor.retrieveStats();
        assertThat(stats)
                .filteredOn("correct", true)
                .hasSize(attemptNumber);
    }

    @ParameterType("correct|incorrect")
    public boolean correct(String input) {
        return "correct".equalsIgnoreCase(input);
    }

}

Mantén las clases step definition simples

Al comparar esta implementación con el borrador que vimos anteriormente, lo primero que notamos es que ahora estamos usando el nivel apropiado de abstracción. Esta vez, mantenemos el estado como una única instancia de campo challengeActor. Agregar esta capa también nos ayudó a mantener esta clase concisa y simple, y enfocada en su responsabilidad principal: mapear pasos Gherkin a métodos Java.

Expresiones de Cucumber

Establecemos el estado dentro del primer método, anotado con @Given. Usa el mapeo:

a new user {word}

Este patrón de texto para emparejar pasos es una expresión de Cucumber. Cualquier paso Gherkin que comience con a new user y sea seguido por una sola palabra será mapeado a este método Java. En esta expresión, {word} es un parámetro de Cucumber. El framework captura esa palabra y la pasa a nuestro método, así que debemos definir en este caso un argumento de tipo String.

Tipos de parámetros

Además de word, hay otros tipos de parámetros integrados que puedes usar en Java: int, float, string, bigdecimal, long, etc. Al usarlos, necesitas emparejar los tipos de argumentos de tu método en consecuencia. Si necesitas capturar texto de una manera más avanzada, Cucumber también soporta Expresiones Regulares.

En nuestro ejemplo, también definimos un Custom Parameter Type, correct. Este método lo define:

    @ParameterType("correct|incorrect")
    public boolean correct(String input) {
        return "correct".equalsIgnoreCase(input);
    }

Como ves, puedes definir tus propios tipos usando la anotación de Cucumber @ParameterType y una expresión regular (correct|incorrect). También podrías usar parámetros de anotación explícitos para definir la expresión regular y el nombre del parámetro. Cuando el nombre no se proporciona, se establece por defecto al nombre del método, en nuestro caso correct. Una vez definido, podemos usarlo en nuestras expresiones, por ejemplo en este método:

    @When("(s)he sends the {correct} challenge solution")
    public void userSendsTheCorrectChallengeSolution(boolean correct) throws Exception {
        this.challengeActor.solveChallenge(correct);
    }

Podrías preferir usar parámetros integrados, pero eso puede hacer que tus escenarios Gherkin sean menos legibles. En nuestro ejemplo, podríamos usar un boolean simple, pero la sentencia debería reformularse a algo como she sends a true challenge solution. No es legible en absoluto.

Intercambiabilidad de anotaciones

Los métodos que implementan Expresiones de Cucumber para emparejar pasos Gherkin deben estar precedidos por una de las anotaciones @Given, @When, @Then, @And, o @But. Sin embargo, la anotación específica que uses es irrelevante para Cucumber. El framework cargará todas las definiciones e intentará mapear pasos sin importar qué preposición o adverbio uses.

En nuestro ejemplo específico, esto significa que podríamos usar cualquiera de estas sentencias, y todas ellas serían mapeadas a la misma step definition:

  • Given she sends the correct challenge solution
  • And she sends the correct challenge solution
  • * she sends the correct challenge solution
  • etc.

Puedes usar, por ejemplo, la anotación que corresponde a la primera sentencia donde usas el paso.

Aserciones

Como planificamos en la Parte 1, estamos usando AssertJ con Cucumber para obtener capacidades de aserción adicionales y (en mi opinión) mejorar la legibilidad del código. Para saber más al respecto, puedes guardar este otro post para más tarde.

En las step definitions de Cucumber, las aserciones deberían limitarse principalmente a los métodos que verifican el resultado esperado (aquellos usualmente anotados con @Then).

Usamos una aserción simple para verificar que los factores están entre los límites esperados:

assertThat(this.challengeActor.getCurrentChallenge().getFactorA())
        .isBetween(9, 100);

Y también usamos una más sofisticada para verificar que el número de intentos exitosos en el historial del usuario coincide con lo que esperamos:

assertThat(stats)
        .filteredOn("correct", true)
        .hasSize(attemptNumber);

En la siguiente parte de esta Guía, veremos cómo combinar AssertJ y Awaitility para ejecutar aserciones en un sistema Eventualmente Consistente.

Ejecutar una característica de Cucumber con Java

Completamos la implementación de todas las step definitions que necesitamos para ejecutar la característica “Solving Challenges”. Recuerda que definimos esta característica usando Gherkin en la Parte 2 (también puedes revisar el código en GitHub). Esto significa que ya podemos ejecutar estos escenarios de prueba.

Primero, necesitas ejecutar el sistema backend. De lo contrario, no tienes nada que probar :) La manera más fácil de hacer esto es descargar el archivo docker-compose-public.yml de los repositorios del libro, y luego ejecutarlo usando Docker Compose.

$ docker-compose -f docker-compose-public.yml up

Dependiendo de los recursos de tu computadora, podría tomar uno o algunos minutos iniciar el conjunto completo de microservicios y herramientas circundantes. Para más instrucciones sobre cómo configurar el sistema backend, revisa el archivo README en el repositorio.

Si todo va bien, el backend debería ser accesible vía el Gateway en localhost:8000. El archivo Docker Compose también incluye el frontend, así que puedes jugar un poco si navegas con tu navegador a localhost:3000.

Ahora, para ejecutar nuestras pruebas de Cucumber por primera vez, podemos usar nuestro IDE o un comando simple de Maven. Mira los ejemplos a continuación de cómo ejecutar las pruebas usando el wrapper de Maven incluido en el repo de GitHub.

# In Mac/Linux
$ ./mvnw test
# In Windows
> mvnw.cmd test

La salida de este comando debería contener algo similar a lo que se muestra a continuación.

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running microservices.book.RunCucumberTest

Scenario: Users get points and badges when solving challenges, and they # microservices/book/leaderboard.feature:4
  Given the following solved challenges                                 # null
  Then Karen has 50 points                                              # null
  * Karen has the "First time" badge                                    # null
  And Laura has 70 points                                               # null
  * Laura has the "First time" badge                                    # null
  * Laura has the "Bronze" badge                                        # null
  And Laura is above Karen in the ranking                               # null

Scenario: Users get new attempts.                        # microservices/book/solving_challenges.feature:6
  Given a new user Mary                                  # microservices.book.cucumber.steps.ChallengeStepDefinitions.aNewUser(java.lang.String)
  When she requests a new challenge                      # microservices.book.cucumber.steps.ChallengeStepDefinitions.userRequestsANewChallenge()
  Then she gets a mid-complexity multiplication to solve # microservices.book.cucumber.steps.ChallengeStepDefinitions.getsAMidComplexityMultiplicationToSolve()

Scenario: Users solve challenges, they get feedback and their stats. # microservices/book/solving_challenges.feature:11
  Given a new user John                                              # microservices.book.cucumber.steps.ChallengeStepDefinitions.aNewUser(java.lang.String)
  And he requests a new challenge                                    # microservices.book.cucumber.steps.ChallengeStepDefinitions.userRequestsANewChallenge()
  When he sends the correct challenge solution                       # microservices.book.cucumber.steps.ChallengeStepDefinitions.userSendsTheCorrectChallengeSolution(boolean)
  Then his stats include 1 correct attempt                           # microservices.book.cucumber.steps.ChallengeStepDefinitions.statsIncludeAttempts(int,boolean)

Scenario: Users get feedback also about incorrect attempts. # microservices/book/solving_challenges.feature:17
  Given a new user Horatio                                  # microservices.book.cucumber.steps.ChallengeStepDefinitions.aNewUser(java.lang.String)
  When he requests a new challenge                          # microservices.book.cucumber.steps.ChallengeStepDefinitions.userRequestsANewChallenge()
  * he sends the incorrect challenge solution               # microservices.book.cucumber.steps.ChallengeStepDefinitions.userSendsTheCorrectChallengeSolution(boolean)
  * he sends the correct challenge solution                 # microservices.book.cucumber.steps.ChallengeStepDefinitions.userSendsTheCorrectChallengeSolution(boolean)
  Then his stats include 1 correct attempt                  # microservices.book.cucumber.steps.ChallengeStepDefinitions.statsIncludeAttempts(int,boolean)
  * his stats include 1 incorrect attempts                  # microservices.book.cucumber.steps.ChallengeStepDefinitions.statsIncludeAttempts(int,boolean)

Tests run: 4, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 3.17 sec <<< FAILURE!

Cucumber ejecuta cuatro escenarios: los tres incluidos en las características de Solving Challenges, pero también el escenario que agregamos a la característica Leaderboard. Por lo tanto, produce algunas fallas porque aún no implementamos las step definitions para esta segunda característica. Incluso genera algunos fragmentos de código que podríamos copiar y pegar en una nueva clase como línea base (mira el código a continuación).

io.cucumber.junit.UndefinedStepException: The step "the following solved challenges" is undefined. You can implement it using the snippet(s) below:

@Given("the following solved challenges")
public void the_following_solved_challenges(io.cucumber.datatable.DataTable dataTable) {
    // Write code here that turns the phrase above into concrete actions
    // For automatic transformation, change DataTable to one of
    // E, List<E>, List<List<E>>, List<Map<K,V>>, Map<K,V> or
    // Map<K, List<V>>. E,K,V must be a String, Integer, Float,
    // Double, Byte, Short, Long, BigInteger or BigDecimal.
    //
    // For other transformations you can register a DataTableType.
    throw new io.cucumber.java.PendingException();
}

Some other steps were also undefined:

@Then("Karen has {int} points")
public void karen_has_points(Integer int1) {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@Then("Karen has the {string} badge")
public void karen_has_the_badge(String string) {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@Then("Laura has {int} points")
public void laura_has_points(Integer int1) {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@Then("Laura has the {string} badge")
public void laura_has_the_badge(String string) {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
@Then("Laura is above Karen in the ranking")
public void laura_is_above_karen_in_the_ranking() {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}

Como podemos ver, Cucumber proporciona no solo algo de código auto-generado básico sino incluso algunos comentarios sobre el uso de DataTable. Además, identificó correctamente algunos parámetros potenciales de Cucumber. Sin embargo, perdió los nombres de usuario, así que creó métodos duplicados con argumentos faltantes. Eso se puede arreglar fácilmente. Podrías usar este enfoque si te gusta, y generar el código basado en tus características Gherkin, pero recuerda estructurar los pasos siguiendo los dominios de tu sistema, y no ponerlos todos juntos en la misma clase.

En cualquier caso, volvamos a los resultados de las pruebas. Los tres escenarios incluidos en nuestra primera característica Solving Challenges se ejecutaron exitosamente, y todos pasaron. Si tienes una terminal decente, también estarán coloreados en verde.

La salida en la terminal donde estás ejecutando el archivo Docker Compose muestra algunas interacciones con el sistema backend. Además, puedes ver el leaderboard si accedes desde tu navegador a la página principal en localhost:3000.

El frontend de la aplicación
Figura 1. El frontend de la aplicación

Ese ya es un gran resultado! Todos los escenarios en nuestra primera característica están pasando, así que nuestro sistema funciona como esperábamos y describimos en nuestro archivo Feature.

Finge hasta que lo logres

Uno de los aspectos clave en BDD y Cucumber es que debes pasar por la fase de Descubrimiento antes que nada, y luego escribir tus características antes de escribir la implementación. Como se describe en la Parte 1, el libro no sigue este enfoque BDD porque es imposible “simular una discusión” dentro del libro, y distraería al lector demasiado temprano de los temas principales.

La próxima vez que estés definiendo nuevas características en tu proyecto, prueba un enfoque BDD real. Si eso no es posible, también puedes usar Cucumber de todos modos para ejecutar tus pruebas end-to-end. Incluso después de que la implementación esté en su lugar, puedes encontrar escenarios que aún no cubriste en tu código.

Reportes de Cucumber

Cucumber nos permite publicar una versión HTML en línea del reporte de resultados de pruebas de forma gratuita. Para habilitar esto, solo necesitamos agregar una propiedad en un nuevo archivo cucumber.properties que podemos colocar dentro del directorio src/main/resources. Mira el código a continuación.

cucumber.publish.enabled=true

Para generar el reporte, solo necesitamos ejecutar nuestras pruebas de nuevo. Esta vez, la salida incluirá un enlace que podemos usar para acceder a los resultados en línea. Mira la Figura 2 para el reporte HTML generado desde mi laptop.

Un reporte de Cucumber disponible en línea
Figura 2. Un reporte de Cucumber disponible en línea

Como podemos ver, muestra cómo tres pruebas (escenarios) pasaron y una de ellas está indefinida (los pasos aún no se implementaron). También da algo de contexto sobre el sistema donde ejecutamos las pruebas. Debajo del resumen, podemos encontrar los detalles de los escenarios que pasaron, y aquellos que no.

Estos reportes se ven bien y son una buena manera de hacer que tus resultados de prueba sean accesibles para otros, especialmente cuando ejecutas las pruebas desde un sistema CI. Sin embargo, ten en cuenta que cualquiera con el enlace puede acceder a ellos (aunque son difíciles de adivinar).

¿Qué sigue?

En la siguiente sección, implementaremos las step definitions para nuestra segunda característica. Estas vienen con un gran desafío: el sistema bajo prueba es Eventualmente Consistente. Cuando un usuario envía un intento exitoso, podría tomar algo de tiempo hasta que el puntaje y las insignias se actualicen en el correspondiente microservicio de Gamification, porque involucra un proceso asíncrono que funciona basado en una Arquitectura Dirigida por Eventos. No queremos escribir pruebas con retrasos que podrían fallar de vez en cuando, así que lidiaremos con esto de manera inteligente.

Master Microservices with Spring Boot

A hands-on guide to building production-ready microservices

Get the Book

Get More Architecture Insights

Join hundreds of software architects and engineers getting practical insights delivered to their inbox.

Volver al Blog