Тестирование

Этот раздел перенесён из документации Camunda 7 и в дальнейшем будет доработан с учётом особенностей OpenBPM Engine

Тестирование BPMN-процессов, CMMN-кейсов (а также DMN-решений) так же важно, как и тестирование программного кода. В этом разделе объясняется, как писать модульные и интеграционные тесты с использованием OpenBPM Engine, а также приводятся лучшие практики и рекомендации.

Модульные тесты

OpenBPM Engine предоставляет вспомогательные классы для написания модульных тестов на JUnit версий 3, 4 и 5.

JUnit 5

В поставку OpenBPM Engine, входит расширение JUnit 5, которое предоставляет доступ к процессному движку и сервисам через getter-методы.

Процессный движок расширения настраивается с помощью стандартного конфигурационного файла operaton.cfg.xml, который должен находиться в classpath. Пользовательский конфигурационный файл может быть передан в расширение при создании объекта ProcessEngineExtension.

Чтобы использовать ProcessEngineExtension для JUnit 5, необходимо добавить следующую зависимость в файл pom.xml:

    <dependency>
      <groupId>io.openbpm.bpm</groupId>
      <artifactId>openbpm-engine-bpm-junit5</artifactId>
      <version>{latest-version}</version>
      <scope>test</scope>
    </dependency>

Следующие примеры кода показывают, как использовать это расширение.

Используйте аннотацию @ExtendWith, чтобы автоматически внедрить процессный движок в объявленное поле.

@ExtendWith(ProcessEngineExtension.class)
public class MyBusinessProcessTest {

  // поле, в которое будет внедрён процессный движок
  ProcessEngine processEngine;

  @Test
  @Deployment
  public void extensionUsageExample() {
    RuntimeService runtimeService = processEngine.getRuntimeService();
    runtimeService.startProcessInstanceByKey("extensionUsage");

    TaskService taskService = processEngine.getTaskService();
    Task task = taskService.createTaskQuery().singleResult();
    assertThat(task.getName()).isEqualTo("My Task");

    taskService.complete(task.getId());
    assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
  }

}

Используйте @RegisterExtension, чтобы создать ссылочный объект ProcessEngineExtension, который предоставляет больше возможностей для конфигурации.

public class MyBusinessProcessTest {

  @RegisterExtension
  ProcessEngineExtension extension = ProcessEngineExtension.builder()
      .configurationResource("myConfig.xml")
      .build();

  @Test
  @Deployment
  public void extensionUsageExample() {
    RuntimeService runtimeService = extension.getRuntimeService();
    runtimeService.startProcessInstanceByKey("extensionUsage");

    TaskService taskService = extension.getTaskService();
    Task task = taskService.createTaskQuery().singleResult();
    assertThat(task.getName()).isEqualTo("My Task");

    taskService.complete(task.getId());
    assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
  }

}

Если вы не хотите создавать конфигурационный файл, процессный движок можно сконфигурировать программно.

public class MyBusinessProcessTest {

  public ProcessEngine myProcessEngine = ProcessEngineConfiguration
      .createStandaloneInMemProcessEngineConfiguration()
      .setJdbcUrl("jdbc:h2:mem:camunda;DB_CLOSE_DELAY=1000")
      .buildProcessEngine();

  @RegisterExtension
  ProcessEngineExtension extension = ProcessEngineExtension
      .builder()
      .useProcessEngine(myProcessEngine)
      .build();

}

JUnit 4

При использовании стиля написания модульных тестов JUnit 4 необходимо использовать ProcessEngineRule. Через это правило процессный движок и сервисы доступны через getter-методы.

Правило ищет стандартный конфигурационный файл operaton.cfg.xml в classpath. При создании объекта ProcessEngineRule можно передать пользовательский конфигурационный файл. Процессные движки кэшируются статически между несколькими модульными тестами при использовании одного и того же конфигурационного ресурса.

Следующий пример демонстрирует использование JUnit 4 и ProcessEngineRule.

public class MyBusinessProcessTest {

  @Rule
  public ProcessEngineRule processEngineRule = new ProcessEngineRule();

  @Test
  @Deployment
  public void ruleUsageExample() {
    RuntimeService runtimeService = processEngineRule.getRuntimeService();
    runtimeService.startProcessInstanceByKey("ruleUsage");

    TaskService taskService = processEngineRule.getTaskService();
    Task task = taskService.createTaskQuery().singleResult();
    assertThat(task.getName()).isEqualTo("My Task");

    taskService.complete(task.getId());
    assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
  }
}

Наши шаблоны проектов для Maven предоставляют полностью готовый к запуску проект, включающий JUnit-тест «из коробки».

JUnit 3

В стиле JUnit 3 необходимо наследоваться от ProcessEngineTestCase. Это делает ProcessEngine и сервисы доступными через защищённые поля класса. В методе setup() теста процессный движок по умолчанию инициализируется с использованием ресурса operaton.cfg.xml из classpath. Чтобы указать другой конфигурационный файл, необходимо переопределить метод getConfigurationResource(). Процессные движки кэшируются статически между несколькими модульными тестами, если используется один и тот же конфигурационный ресурс.

Пример теста в стиле JUnit 3:

public class MyBusinessProcessTest extends ProcessEngineTestCase {

  @Deployment
  public void testSimpleProcess() {
    runtimeService.startProcessInstanceByKey("simpleProcess");

    Task task = taskService.createTaskQuery().singleResult();
    assertThat(task.getName()).isEqualTo("My Task");

    taskService.complete(task.getId());
    assertThat(runtimeService.createProcessInstanceQuery().count()).isEqualTo(0);
  }
}

Деплой тестовых ресурсов

Тестовые классы и методы можно аннотировать с помощью @Deployment. Перед выполнением теста будет задеплоен ресурс с именем TestClassName.bpmn20.xml (для аннотации на уровне класса) или TestClassName.testMethod.bpmn20.xml (для аннотации на уровне метода), расположенный в том же пакете, что и тестовый класс. По завершении теста деплой будет удалён вместе со всеми связанными экземплярами процессов, задачами и т.д. Аннотация @Deployment также поддерживает явное указание расположения ресурсов.

@Deployment(resources = {"myProcess.bpmn", "mySubprocess.bpmn"})

В этом случае файлы myProcess.bpmn и mySubProcess.bpmn будут взяты непосредственно из корня classpath.

Аннотации на уровне метода переопределяют аннотации на уровне класса. Подробнее см. Javadoc для @Deployment.

Аннотация поддерживается для стилей тестирования JUnit 3 и JUnit 4.

Указание требуемого уровня истории

Если тесту требуется определённый уровень истории (например, потому что используется HistoryService), можно аннотировать тестовый класс или метод с помощью @RequiredHistoryLevel и указать необходимый уровень истории (например, "activity", "full"). Перед выполнением теста текущий уровень истории процессного движка проверяется, и тест будет пропущен, если уровень истории ниже указанного.

Пример теста в стиле JUnit 4:

public class MyBusinessProcessTest {

  @Rule
  public ProcessEngineRule processEngineRule = new ProcessEngineRule();

  @Test
  @Deployment
  @RequiredHistoryLevel(ProcessEngineConfiguration.HISTORY_ACTIVITY)
  public void ruleUsageExample() {
    RuntimeService runtimeService = processEngineRule.getRuntimeService();
    runtimeService.startProcessInstanceByKey("ruleUsage");

    HistoryService historyService = processEngineRule.getHistoryService();
    // требуется уровень истории >= "activity"
    HistoricVariableInstance variable = historyService
      .createHistoricVariableInstanceQuery()
      .singleResult();

    assertEquals("value", variable.getValue());
  }
}

Аннотация поддерживается для стилей тестирования JUnit 3 и JUnit 4. Обратите внимание, что пропущенный тест помечается как успешно пройденный для JUnit 3, поскольку JUnit 3 не поддерживает механизм пропуска тестов.

Отладка модульных тестов

При использовании in-memory базы данных H2 для модульных тестов следующие инструкции позволяют легко просматривать данные в базе процессного движка во время отладочной сессии. Скриншоты приведены для Eclipse, но механизм аналогичен и в других IDE.

Предположим, что мы установили breakpoint где-то в модульном тесте. В Eclipse это делается двойным щелчком по левому полю рядом с кодом:

API Test Debugging

Если теперь запустить тест в режиме отладки (правый клик по тестовому классу → «Run as» → «JUnit test»), выполнение теста остановится на breakpoint, где можно просмотреть переменные теста в правой верхней панели.

API Test Debugging

Чтобы просмотреть данные, откройте окно «Display» (если его нет, выберите Window → Show View → Other и выберите Display) и введите (доступно автодополнение): org.h2.tools.Server.createWebServer("-web").start()

API Test Debugging

Выделите введённую строку и выполните «Display» через контекстное меню или горячую клавишу.

API Test Debugging

Затем откройте браузер и перейдите по адресу http://localhost:8082, укажите JDBC URL in-memory базы данных (по умолчанию jdbc:h2:mem:camunda) и нажмите кнопку подключения.

API Test Debugging

Теперь вы можете просматривать базу данных процессного движка и использовать её для понимания того, как и почему модульный тест выполняет процесс определённым образом.

API Test Debugging

Assert-расширения OpenBPM Engine

В дополнение к стандартным JUnit-assertion’ам, OpenBPM Engine Assert предоставляет fluent API для проверки типичных сценариев в процессах, интегрируясь с AssertJ https://joel-costigliola.github.io/assertj/.

assertThat(processInstance).isWaitingAt("UserTask_InformCustomer");
assertThat(task()).hasCandidateGroup("Sales").isNotAssigned();

Более подробное руководство с примерами доступно по ссылке Примеры Assert.

Чтобы использовать OpenBPM Engine Assert, добавьте следующую зависимость в pom.xml:

<dependency>
  <groupId>io.openbpm.bpm</groupId>
  <artifactId>openbpm-engine-bpm-assert</artifactId>
  <version>{latest-version}</version>
  <scope>test</scope>
</dependency>

Также необходимо добавить библиотеку AssertJ. Убедитесь, что используется корректная версия. Подходящую версию можно найти в матрице совместимости ниже.

<dependency>
  <groupId>org.assertj</groupId>
  <artifactId>assertj-core</artifactId>
  <version>{latest-version}</version>
  <scope>test</scope>
</dependency>

Если OpenBPM Engine Assert используется совместно с Spring Boot или OpenBPM Engine Spring Boot Starter, зависимость AssertJ, как правило, уже присутствует в проекте.

Совместимость версий Assert

Каждая версия OpenBPM Engine Assert привязана к конкретной версии OpenBPM Engine и AssertJ. Только эти стандартные комбинации рекомендуются (и поддерживаются) OpenBPM Engine. Тем не менее, каждую версию OpenBPM Engine Assert можно использовать с более новыми patch-версиями движка OpenBPM Engine, однако такие комбинации должны быть тщательно протестированы перед использованием в продакшене. Все версии до 3.0.0 относятся к community-расширениям и не входят в официальный контур поддержки OpenBPM Engine. Начиная с OpenBPM Engine 7.17.0 проект был перенесён в репозиторий OpenBPM Engine и в дальнейшем использует ту же схему версионирования, что и OpenBPM Engine.

OpenBPM Engine Assert artifact Версия AssertJ Версия OpenBPM Engine Assert Версия OpenBPM Engine

openbpm-engine-bpm-assert

3.26.3

2025.0.0

2025.0.0

openbpm-engine-bpm-assert

3.27.7

2026.0.0

2026.0.0

Community-расширения для поддержки тестирования

Существует несколько хорошо задокументированных и широко используемых community-расширений, которые делают тестирование более продуктивным и удобным.

Camunda Engine Scenario Tests

Camunda-bpm-assert-scenario https://github.com/camunda/camunda-bpm-assert-scenario/ позволяет писать более устойчивые наборы тестов. Идея заключается в том, что тесты требуется изменять только тогда, когда модели процессов меняются таким образом, что это влияет на проверяемое поведение. Основной акцент делается не на конкретном пути выполнения процесса, а на внешних эффектах этого пути.

@Test
public void testHappyPath() {
  // часть "given"
  when(process.waitsAtUserTask("CompleteWork")).thenReturn(
    (task) -> task.complete()
  );
  // часть "when"
  run(process).startByKey("ReadmeProcess").execute();
  // часть "then"
  verify(process).hasFinished("WorkFinished");
}

Camunda Test Coverage

Camunda-bpm-process-test-coverage https://github.com/camunda/camunda-bpm-process-test-coverage/ визуализирует пути выполнения процессов в тестах и проверяет коэффициент покрытия модели процесса. При выполнении стандартных JUnit-тестов в каталоге сборки генерируются HTML-файлы.

Разрешение бинов без Spring/CDI

Класс Mocks можно использовать для того, чтобы сделать бины доступными внутри Expression Language или в Script Tasks без необходимости использования менеджера бинов.

Регистрация бина в приложении:

Mocks.register("myBean", new Bean());

После этого именованный бин становится доступным и может использоваться внутри процесса:

<serviceTask id="serviceTask" camunda:expression="#{myBean.invokeMethod()}" />

Если замокированные бины должны быть доступны уже на этапе деплоя процесса (например, выражение бина в определении таймерного стартового события), необходимо убедиться, что они зарегистрированы до выполнения деплоя. Например, при использовании аннотации @Deployment бины не следует регистрировать в методе @Before. Вместо этого можно создать отдельное тестовое правило, которое регистрирует бины при старте, и связать его в цепочку перед ProcessEngineRule.

Функциональность замокированных бинов предназначена только для целей тестирования. Бины, зарегистрированные через Mocks, доступны исключительно в рамках потока, в котором они были сохранены, так как используется ThreadLocal. В большинстве продуктивных окружений доступ к таким бинам во время выполнения процесса невозможен, поскольку задания выполняются многопоточным Job Executor’ом. Так как Job Executor отключён в сценариях модульного тестирования, поток выполнения процесса совпадает с потоком, в котором создаются экземпляры замокированных бинов.

Лучшие практики

Пишите сфокусированные тесты

Возможность запускать экземпляр процесса, начиная с определённых активностей, позволяет создавать очень специфические сценарии тестирования с минимальной подготовкой.

Ограничение области тестирования (Scoping)

BPMN-процессы, CMMN-кейсы и DMN-решения не существуют изолированно. Рассмотрим пример BPMN-процесса: во-первых, сам процесс выполняется процессным движком OpenBPM Engine, которому требуется база данных. Далее, процесс — это «не просто процесс». Он может содержать выражения, скрипты и часто вызывать пользовательские Java-классы, которые, в свою очередь, могут обращаться к сервисам — локальным или удалённым. Чтобы протестировать процесс, все эти компоненты должны быть доступны, иначе тест не сможет выполниться.

Настройка всей этой инфраструктуры ради одного модульного теста является затратной. Поэтому на практике имеет смысл применять концепцию, называемую ограничением области тестирования (test scoping). Суть в том, чтобы ограничить объём инфраструктуры, необходимой для выполнения теста. Всё, что выходит за рамки области тестирования, подменяется моками.

Пример: ограничение области тестирования для Java EE-приложения

Рассмотрим пример. Предположим, вы разрабатываете типичное Java EE-приложение, содержащее BPMN-процесс. Процесс использует Java Expression Language (EL) для условий, вызывает реализации JavaDelegate как CDI-бины, которые, в свою очередь, могут обращаться к бизнес-логике, реализованной в виде EJB. Бизнес-логика использует JPA для управления дополнительными бизнес-объектами во вторичной базе данных. Также приложение отправляет сообщения через JMS для взаимодействия с внешними системами и имеет веб-интерфейс. Всё это работает внутри Java EE-сервера приложений, такого как WildFly.

Для тестирования такого приложения необходимо наличие всех компонентов, включая сам сервер приложений, а также внешние системы, обрабатывающие JMS-сообщения. Это затрудняет написание сфокусированных тестов. Однако, анализируя сам процесс, можно обнаружить множество аспектов, которые можно протестировать без развёртывания всей инфраструктуры. Например, при наличии процессных данных условия на Expression Language часто можно тестировать без дополнительной инфраструктуры. Это уже позволяет проверить, что процесс «выбирает правильный путь» на шлюзе при заданных входных данных. Далее, если EJB-компоненты замокированы, в тест можно включить и логику делегирования. Это позволяет проверить корректность связывания делегатов, корректность преобразования и маппинга данных, а также вызов бизнес-логики с правильными параметрами. Поскольку процессный движок OpenBPM Engine может работать с in-memory базой данных, становится возможным тестировать BPMN-процесс «в изоляции» как модульный тест и проверять его локальную функциональную корректность. Тот же принцип может быть применён и к следующим «внешним слоям» системы, включая бизнес-логику и внешние системы.

Следующая схема иллюстрирует это на примере Java EE-приложения:

Testing Scopes

Выделяются три области тестирования:

  • Область 1: локальная функциональная корректность модели процесса с данными, условиями и кодом делегатов — обычно реализуется в виде модульного теста.

  • Область 2: интеграция с бизнес-логикой внутри runtime-контейнера — для Java EE-приложений обычно реализуется как интеграционный тест на базе Arquillian.

  • Область 3: интеграция с внешними системами и пользовательским интерфейсом.

Следует отметить, что приведённый пример относится к Java EE-приложению; для других типов приложений области тестирования могут отличаться. Однако сам принцип остаётся неизменным.

Лицензия и атрибуция

Эта документация была создана на базе материала "Camunda 7 Docs" от Camunda, находится под лицензией Creative Commons Attribution-ShareAlike 3.0 Unported License .

Оригинал документации: https://docs.camunda.org