
Cucumber – Ejemplo práctico
Cucumber es una herramienta que soporta BDD y que nos ha permitido crear pruebas de fácil entendimiento para personas no técnicas, debido al uso del lenguaje Gherkin.
Esto permitió que el equipo del producto pueda validar rápidamente el output esperado de los escenarios dados y hasta atreverse a crear sus propias pruebas debido a la facilidad del lenguaje.
Recientemente hemos utilizado esta herramienta y nos ha ayudado mucho en la verificación de las funcionalidades de nuestro proyecto que eran complejas bajo ciertos escenarios.
En este artículo creamos un ejemplo práctico para gestionar inventarios donde podemos reubicar sus elementos entre diferentes ubicaciones destino.
Dependencias Maven
Agregamos las siguientes dependencias en nuestro archivo pom.xml
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.9.0</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>7.9.0</version>
</dependency>
La Clase
Para la gestión del inventario creamos la clase Inventory.java, InventoryLine.java e Item.java
La clase inventory contiene los inventory lines y el método relocate que se encarga de reubicar los items existentes a una nueva ubicación.
public class Inventory {
private Set<InventoryLine> inventoryLines;
public boolean relocate(String itemCode, Integer qty, String
sourceLocation, String targetLocation) {
var item = new Item(itemCode);
InventoryLine line = inventoryLines
.stream()
.filter(l -> l.getItem().equals(item)
&& StringUtils.equals(l.getLocation(), sourceLocation)
&& l.getQuantity().compareTo(qty) >= 0)
.findFirst().orElse(null);
if (line == null) {
return false;
}
if (line.getQuantity().compareTo(qty) == 0) {
line.setLocation(targetLocation);
} else {
line.reduceQuantity(qty);
var newLine = new InventoryLine();
newLine.setItem(item);
newLine.setLocation(targetLocation);
newLine.setQuantity(qty);
inventoryLines.add(newLine);
}
return true;
}
}
La clase InventoryLine e Item son simples beans que representan a los objetos de la vida real.
public class Item {
private String itemCode;
}
public class InventoryLine {
private Item item;
private Integer quantity;
private String location;
}
El Escenario
Los escenarios son creados en archivos con extensión .feature y en lenguaje Gherkin como había sido mencionado anteriormente.
El siguiente escenario nos ayuda a validar los resultados esperados del inventario después de una reubicación de los items.
Se observan 3 pasos muy importantes,
Given.- describe el contexto inicial de la aplicación, en este caso el inventario inicial.
When.- describe el evento de reubicación del item del location 1 al location 5.
Then.- describe el resultado esperado.
Feature: Testing the relocation logic
Users should be able to relocate the items of an inventory
Scenario: Move items to other location
Given an inventory with the following items
| item code | qty | location |
| item-1 | 10 | loc1 |
| item-2 | 20 | loc2 |
| item-3 | 30 | loc3 |
| item-4 | 40 | loc4 |
When users relocate the following inventory to the location "loc5"
| item code | qty | location |
| item-1 | 10 | loc1 |
Then the expected inventory is the following
| item code | qty | location |
| item-1 | 10 | loc5 |
| item-2 | 20 | loc2 |
| item-3 | 30 | loc3 |
| item-4 | 40 | loc4 |
Cuando ejecutemos el escenario, cucumber buscara la implementación del step en nuestras clases para saber que lógica se va a ejecutar, por ende necesitamos definir la lógica de cada paso usando diferentes métodos que harán match con el paso haciendo uso de las anotaciones.
Para este ejemplo usamos una clase donde se encuentran nuestras definiciones.
La clase stepDefinition que contiene la definición de todos los pasos.
public class StepDefinition {
private Inventory inventory = new Inventory();
@Given("an inventory with the following items")
public void anInventoryWithTheFollowingItems(List<InventoryLine> inventoryLines) {
inventory.setInventoryLines(new HashSet<>(inventoryLines));
}
@When("users relocate the following inventory to the location {string}")
public void relocate(String targetLocation, List<InventoryLine> inventoryLines) {
var inventoryLine = inventoryLines.get(0);
inventory.relocate(inventoryLine.getItem().getItemCode(), inventoryLine.getQuantity(),
inventoryLine.getLocation(), targetLocation);
}
@Then("the expected inventory is the following")
public void ss(List<InventoryLine> expectedInventoryLines) {
assertNotNull(inventory.getInventoryLines());
assertEquals(expectedInventoryLines.size(),inventory.getInventoryLines().size());
for(var line : inventory.getInventoryLines()) {
var expectedLine = expectedInventoryLines.stream().filter(l-> StringUtils.equals(l.getLocation(), line.getLocation())).findFirst().orElse(null);
assertNotNull(expectedLine);
assertEquals(expectedLine.getItem(), line.getItem());
assertEquals(expectedLine.getQuantity(), line.getQuantity());
}
}
@DataTableType
public InventoryLine mapInventoryLine(Map<String, String> data) {
var line = new InventoryLine();
line.setItem(new Item(data.get("item code")));
line.setQuantity(Integer.valueOf(data.get("qty")));
line.setLocation(data.get("location"));
return line;
}
}
Ejecutamos los escenarios y obtenemos la siguiente salida en la consola, que como se observa las pruebas fueron ejecutadas satisfactoriamente.
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.indevsolutions.example.cucumber.CucumberIntegrationTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.807 s - in com.indevsolutions.example.cucumber.CucumberIntegrationTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
Como se observa, el uso de cucumber puede ser de mucha ayuda y al ser entendible por personas no técnicas podemos involucrar al equipo del producto para realizar las validaciones y hasta crear más escenarios.
Como siempre puedes encontrar el código del ejemplo aquí.
Con esto nos despedimos y esperamos que hayamos podido contribuir en algo compartiendo nuestro conocimiento.