Cuando no se realiza un buen testing el costo de arreglar un error en producción es elevado, por lo cual aplicar una buena técnica de testing nos ayuda a dormir mejor en las noches, para ello, poder incluir al equipo de desarrollo y negocio en las pruebas automatizadas será un gran avance para mantener el código y los requerimientos bajo control, aquí nace las pruebas de aceptación y BDD, una buena implementación y planteamiento de requerimientos es fundamental en esta etapa de desarrollo.
Como antecedente, comprendamos que Cucumber es un software de testing TDD que nos permitirá elaborar pruebas automáticas a partir de criterios de aceptación fácilmente entendibles por los intervinientes en el proceso, el cual vamos a implementar en éste tutorial, así como Spring Boot y JUnit.
Para iniciar, necesitamos un proyecto básico con Spring Boot 2, en esta oportunidad lo crearemos con Spring Initializer, un API que permite la generación de proyectos con sus dependencias permitiendo simplificar esta etapa de arranque, aunque también se puede utilizar el plugin de Spring Boot de IntelliJ o Visual Studio Code.
Usaremos la siguiente configuración:
Configuración inicial con Spring Boot Initializer
-
Configuración de Cucumber y Junit 5
En este paso agregaremos las dependencias de maven (librerías que se necesita para el funcionamiento correcto de Cucumber con Spring y Junit).
1.1 Dependencias de Maven (pom.xml)
1.1.1 Cucumber JVM
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.8.1</version>
<scope>test</scope>
</dependency>
Podemos encontrar el listado de versiones aquí.
1.1.2 Junit y Cucumber
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>7.8.1</version>
<scope>test</scope>
</dependency>
Podemos encontrar el listado de versiones aquí.
1.1.3 Junit platform suite (ya que la opción @Cucumber de Junit4 quedó obsoleta)
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
Podemos encontrar el listado de versiones aquí.
1.1.4 Finalmente Spring y Cucumber
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>7.8.1</version>
<scope>test</scope>
</dependency>
Podemos encontrar el listado de versiones aquí.
-
Definición e implementación de “Features”
Para esta definición, lo más usual es utilizar Gherkin; al ser de alto nivel, no es tan técnico, por lo que, es posible involucrar al personal de negocio.
Creamos el archivo demo.feature en src/test/java/<paquete>/cucumber/features con el siguiente contenido:
Feature: the hello endpoint can be retrieved
Scenario: client makes call to GET greeting endpoint
Given client wants to write username
When client calls greeting endpoint
Then client receives hello text and username
El contenido no debe contener muchas definiciones técnicas, recordemos que el objetivo es que todo el equipo se involucre.
Definición de pasos:
Given: Dada una situación en la que configuramos el test.
When: Acción que se realiza o un evento llega.
Then: Espero que suceda algo o puedo comprobarlo.
-
Configuración de puntos de entrada
La siguiente clase nos sirve como punto de entrada de Cucumber para ejecutar los tests, la podemos llamar de cualquier forma pero siempre tomando en cuenta las convenciones.
Creamos la clase CucumberTest.java en src/test/java/<paquete>/cucumber con el siguiente contenido:
@Suite
@IncludeEngines(«cucumber»)
@SelectClasspathResource(«com/sevolutivo/demo/cucumber/features»)
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = «com.sevolutivo.demo.cucumber.stepDefs»)
public class CucumberTest {
}
@Suite y @IncludeEngines(«cucumber») son el equivalente a la antigua @Cucumber, pero que con la versión 7 de Cucumber quedó obsoleta.
En JUnit 5, podemos definir las opciones de cucumber en el fichero junit-platform.properties, en la carpeta resources de tests.
@SelectClasspathResource(«<paquete>/cucumber/features») nos dice dentro de la carpeta resources donde tenemos los ficheros .feature
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = «<paquete>.cucumber.stepDefs») le decimos donde están las clases de pegamento, es decir, los steps.
-
Configuración opcional adicional (junit-platform.properties)
Creamos el archivo junit-platform.properties en src/test/resources con el siguiente contenido:
cucumber.publish.quiet=true
cucumber.publish.enabled=false
cucumber.plugin=pretty, html:target/cucumber-reports/Cucumber.html, json:target/cucumber-reports/Cucumber.json,junit:target/cucumber-reports/Cucumber.xml
En el ejemplo anterior, estamos quitando el banner que aparece al ejecutar cucumber y le decimos que no publique los resultados en su servicio de la nube. También estamos generando los informes localmente en formato html, json y xml en el directorio target/cucumber-reports.
-
Configuración de Spring
Vamos a configurar Cucumber para que pueda hacer uso del contexto de Spring con la notación @SpringBootTest, vamos a inyectar también TestRestTemplate para hacer llamadas Rest.
Creamos el archivo CucumberSpringConfiguration.java en src/test/java/<paquete>/cucumber:
@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CucumberSpringConfiguration {
@Autowired
protected TestRestTemplate testRestTemplate;
}
Para comprobar el correcto funcionamiento ejecutamos ./mvnw clean verify en consola y el resultado debería ser similar a lo siguiente:
Podemos comprobar que no encuentra la definición de pasos y nos sugiere implementarlos de acuerdo a sus recomendaciones.
-
Implementación de pasos (steps ó glue code)
Los pasos o steps es el mapeo de cada paso del escenario definido en el archivo .feature, ésta clase tiene la notación @CucumberContextConfiguration que hace que Cucumber la use como configuración de contexto de Spring.
Creamos el archivo DemoSteps.java en src/test/<paquete>/cucumber/stepDefs:
@CucumberContextConfiguration
public class DemoSteps extends CucumberSpringConfiguration {
private String username;
private ResponseEntity response;
@Given(«client wants to write username»)
public void clientWantsToWriteAGreeting(){
username = «user001»;
}
@When(«client calls greeting endpoint»)
public void clientCallsToGreetingEndpoint() {
response = testRestTemplate.getForEntity(«/greeting/» + username, String.class);
}
@Then(«client receives hello text and username»)
public void clientReceivesHelloTextAndGreeting() {
Assertions.assertEquals(«hello « + username, response.getBody());
}
}
-
Implementación de Rest API controller
Creamos una clase controlador DemoController.java con un método GET /greeting en src/main/java/<paquete>/web que retorna una cadena con un saludo:
@RestController
public class DemoController {
@GetMapping(«/greeting/{username}»)
public String greeting(@PathVariable String username){
return «hello « + username;
}
}
-
Ejecución
Una vez listo toda la implementación, ejecutar ./mvnw clean verify y obtendremos lo siguiente:
Si se usa IntelliJ Idea, al ejecutar los test de Cucumber implementados, como resultado se obtendrá algo similar a esto:
-
Conclusiones
Los tests de aceptación son una herramienta importante si el objetivo es integrar al equipo de negocio y al equipo de desarrollo, mejorando así el planteamiento de los escenarios, requerimientos y casos de análisis.
En éste tutorial hemos planteado un caso básico de implementación y la configuración de Cucumber, Spring Boot y JUnit 5 y así obtener una arquitectura básica o referencial para futuras implementaciones.
El código fuente se puede encontrar en github.com.
Porque se utiliza SpringBoot ?