Привет! Меня зовут Анатолий, я ведущий разработчик в ITFB Group, и сегодня я хочу рассказать о том, как можно превратить скучные тесты в главного специалиста по продукту.

Проблема: ваши тесты — это китайская грамота для бизнеса
Представьте: ваши JUnit-тесты проходят, CI/CD зелёный, все довольны. Но тут приходит бизнес-аналитик и спрашивает: "А этот сценарий проверяет, что будет, если клиент с рейтингом 700 запросит 10 миллионов?" Вы начинаете лихорадочно копаться в коде, пытаясь найти тот самый @Test...
Знакомая картина? А что если бы ответ на этот вопрос лежал не в глубинах Java-кода, а в красивом, читаемом файле, который понятен всем — от тимлида до заказчика?
Сегодня я расскажу, как можно превратить тестирование из технической рутины в живой диалог между командами.
Что мы получаем, добавляя Cucumber и Allure?
Единый язык для команды: Сценарии на языке Gherkin становятся мостом между аналитиками, тестировщиками и разработчиками
Живая и исполняемая документация: Feature-файлы всегда актуальны, так как являются самим кодом тестов
Прозрачность и наглядность: Allure-отчеты делают результаты прогона тестов интуитивно понятными для всех участников проекта
Минимальные затраты на внедрение: Мы не меняем основной стек разработки, а лишь надстраиваем над ним удобный и стандартизированный слой BDD
Собираем наш BDD-бургер (Cucumber, JUnit, Allure)

Давайте посмотрим на наши главные ингредиенты:
Инструмент |
Роль в экосистеме |
Cucumber |
Интерпретирует сценарии на языке Gherkin и связывает их с Java-кодом |
JUnit |
Организует запуск тестовых сценариев |
Allure |
Визуализирует результаты выполнения в виде детальных и наглядных отчетов |
AssertJ |
Обеспечивает удобные и читаемые утверждения |
Настройка проекта: добавляем специи к существующему стеку
Добавляем необходимые зависимости в pom.xml:
<properties>
<cucumber.version>7.14.0</cucumber.version>
<allure.version>2.21.0</allure.version>
</properties>
<dependencies>
<!-- Cucumber -->
<dependency>
<groupId>org.camunda.bpm</groupId>
<artifactId>camunda-engine</artifactId>
<version>7.21.0</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
<!-- Интеграция Allure с Cucumber — формирует отчёты о прохождении тестов -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-cucumber7-jvm</artifactId>
<version>${allure.version}</version>
<scope>test</scope>
</dependency>
<!-- Cucumber Spring — связывает Cucumber со Spring-контекстом (инжекция бинов в step definitions) -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>7.16.1</version>
<scope>test</scope>
</dependency>
<!-- Cucumber JUnit Platform Engine — интеграция Cucumber с JUnit 5 (через JUnit Platform) -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>7.16.1</version>
<scope>test</scope>
</dependency>
<!-- Cucumber Java: реализация шагов на Java для сценариев на языке Gherkin -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.16.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Surefire — запуск юнит-тестов и передача параметров Allure -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<systemProperties>
<property>
<name>allure.results.directory</name>
<value>${project.build.directory}/allure-results</value>
</property>
</systemProperties>
</configuration>
</plugin>
<!-- Плагин Allure для Maven — генерирует HTML-отчёты из результатов тестов -->
<plugin>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-maven</artifactId>
<version>2.13.0</version>
<configuration>
<reportVersion>${allure.version}</reportVersion>
</configuration>
</plugin>
</plugins>
</build>
Структура проекта остаётся логичной:
src/test/
├── java/
│ └── com/example/tests/
│ ├── runner/
│ │ └── RunCucumberTest.java
│ └── steps/
│ └── LoanApprovalSteps.java
└── resources/
└── features/
└── loan-approval.feature
Бизнес-кейс: кредитная заявка

Рассмотрим типичный бизнес-кейс — систему одобрения кредитов. У нас есть простой процесс в Camunda:
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
id="Definitions_1"
targetNamespace="http://camunda.org/examples"
exporter="Camunda Modeler"
exporterVersion="5.22.0">
<bpmn:collaboration id="Collaboration_0ywjglb">
<bpmn:participant id="Participant_0j75s7a" name="loanApproval" processRef="loanApproval" />
</bpmn:collaboration>
<bpmn:process id="loanApproval" name="Loan Approval Process" isExecutable="true" camunda:historyTimeToLive="180">
<bpmn:startEvent id="StartEvent" name="Loan Application Received">
<bpmn:outgoing>Flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:serviceTask id="CheckCreditHistoryTask" name="Check Credit History" camunda:delegateExpression="${checkCreditHistoryDelegate}">
<bpmn:incoming>Flow_1</bpmn:incoming>
<bpmn:outgoing>Flow_2</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:userTask id="ApproveLoanTask" name="Approve Loan Application" camunda:assignee="managers">
<bpmn:extensionElements>
<camunda:taskListener event="create" delegateExpression="${completeTaskUserListener}" />
</bpmn:extensionElements>
<bpmn:incoming>Flow_2</bpmn:incoming>
<bpmn:outgoing>Flow_3</bpmn:outgoing>
</bpmn:userTask>
<bpmn:endEvent id="EndEvent" name="Loan Approved">
<bpmn:incoming>Flow_3</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1" sourceRef="StartEvent" targetRef="CheckCreditHistoryTask" />
<bpmn:sequenceFlow id="Flow_2" sourceRef="CheckCreditHistoryTask" targetRef="ApproveLoanTask" />
<bpmn:sequenceFlow id="Flow_3" sourceRef="ApproveLoanTask" targetRef="EndEvent" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0ywjglb">
<bpmndi:BPMNShape id="Participant_0j75s7a_di" bpmnElement="Participant_0j75s7a" isHorizontal="true">
<dc:Bounds x="156" y="93" width="600" height="250"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="StartEventShape" bpmnElement="StartEvent">
<dc:Bounds x="240" y="200" width="36" height="36"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="CheckCreditHistoryTaskShape" bpmnElement="CheckCreditHistoryTask">
<dc:Bounds x="340" y="180" width="120" height="80"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ApproveLoanTaskShape" bpmnElement="ApproveLoanTask">
<dc:Bounds x="520" y="180" width="120" height="80"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEventShape" bpmnElement="EndEvent">
<dc:Bounds x="700" y="200" width="36" height="36"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1_di" bpmnElement="Flow_1">
<di:waypoint x="276" y="218"/>
<di:waypoint x="340" y="218"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_2_di" bpmnElement="Flow_2">
<di:waypoint x="460" y="218"/>
<di:waypoint x="520" y="218"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_3_di" bpmnElement="Flow_3">
<di:waypoint x="640" y="218"/>
<di:waypoint x="700" y="218"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
Ключевые бизнес-правила (те самые, которые все понимают по-разному):
● Автоматический отказ при низком кредитном рейтинге
● Одобрение менеджера для крупных сумм даже при хорошей истории
● Автоматическое одобрение для небольших сумм надежным клиентам
Магия Gherkin: Как бизнес-правила становятся кодом (и все всё понимают)
Вот где начинается настоящая магия! Мы явно описываем бизнес-правила в сценариях.
Файл: loan-approval.feature
Реализация шагов: где магия встречается с кодом
А вот как мы связываем эти красивые сценарии с реальным кодом:
package com.example.cucumberex.cucumber;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.history.HistoricVariableInstance;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
/*
Cucumber Step Definitions для тестирования процесса Camunda BPM.
Он связывает сценарии Cucumber (Given/When/Then) с кодом.
Через RuntimeService запускает процесс «loanApproval».
Через HistoryService проверяет результат (одобрен или отклонён кредит).
Использует аннотации @Given, @When, @Then для соответствия шагам сценария.
*/
public class LoanApprovalSteps {
@Autowired
private RuntimeService runtimeService;
@Autowired
private HistoryService historyService;
private ProcessInstance processInstance;
private String clientName;
private Long loanAmount;
@Given("a client {string} submits a loan application for {long}")
public void clientSubmitsApplication(String clientName, Long loanAmount) {
this.clientName = clientName;
this.loanAmount = loanAmount;
Map<String, Object> variables = new HashMap<>();
variables.put("clientName", clientName);
variables.put("loanAmount", loanAmount);
processInstance = runtimeService.startProcessInstanceByKey("loanApproval", variables);
assertThat(processInstance).isNotNull();
}
@When("the process is executed")
public void processIsExecuted() {
// User task will be automatically completed by TaskListener
}
@Then("the loan should be approved")
public void loanShouldBeApproved() {
assertThat(processInstance).isNotNull();
HistoricVariableInstance loanApprovedVar =
historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId())
.variableName("loanApproved")
.singleResult();
assertThat(loanApprovedVar).isNotNull();
Boolean loanApproved = (Boolean) loanApprovedVar.getValue();
assertThat(loanApproved).isTrue();
}
@Then("the loan should be rejected")
public void loanShouldBeRejected() {
assertThat(processInstance).isNotNull();
HistoricVariableInstance loanApprovedVar =
historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId())
.variableName("loanApproved")
.singleResult();
assertThat(loanApprovedVar).isNotNull();
Boolean loanApproved = (Boolean) loanApprovedVar.getValue();
assertThat(loanApproved).isFalse();
}
}
Он создаст интерактивный отчет, который покажет:
● Какие сценарии прошли, а какие нет
● Детализацию по каждому шагу
● Значения переменных в момент теста
● Время выполнения каждого шага
Это делает процесс тестирования абсолютно прозрачным. Больше никаких "а у меня на машине работало!".
После успешного выполнения тестов запускается команда mvn allure:serve. Эта команда генерирует Allure-отчёт из полученных результатов и автоматически запускает локальный веб-сервер, а затем открывает сгенерированный index.html файл в браузере по умолчанию.
Отчёт содержит несколько ключевых страниц:
-
Overview: На главной странице отображается сводка по запуску — общее количество тестов, процент успешного выполнения и график с историей запусков.

-
Suites: Вкладка, которая отображает все тесты в виде иерархической структуры, соответствующей организации кода (пакеты, классы, методы). Она позволяет быстро найти тесты в конкретном классе или пакете, в отличие от бизнес-ориентированного представления в Behaviors. Это основной вид для анализа результатов с точки зрения структуры проекта, а не функциональности.

-
Graphs: Раздел содержит различные диаграммы, которые визуализируют результаты тестирования, распределяя тесты по статусам (passed, failed, broken и т.д.), а также анализируют их продолжительность.

-
Timeline: На этой вкладке представлена хронология выполнения тестов, что позволяет оценить общие временные затраты и увидеть, на каких устройствах или в каких средах производился запуск.

Итоги: небольшое улучшение с большой выгодой
Итак, что мы получили в итоге? Мы не просто "настроили Cucumber". Мы подарили проекту общую нервную систему.
● Единый язык: Бизнес-правила теперь явно описаны в сценариях, а не спрятаны в коде
● Живая документация: loan-approval.feature — это и тест, и спецификация, которая всегда актуальна
● Прозрачность процессов: Allure-отчёты делают ход теста понятным для не-технарей
● Усиление регрессионного тестирования: Легко добавлять новые сценарии, не ломая старые
● Минимальные затраты: Мы не переписывали тесты с нуля, а лишь надстроили над ними BDD-слой
Это не революция. Это — эволюция. Небольшая надстройка над вашим текущим стеком, которая даёт колоссальный эффект. Потратьте день на интеграцию, чтобы сэкономить десятки часов в будущем на объяснениях, поиске багов и переписывании "мёртвой" документации.
Как говорят у нас в ITFB Group: "Лучший код — это не только работающий код, но и код, который все понимают." А с BDD это понимание становится реальностью.