Привет. На связи Вероника. Ранее подробно ответила на вопрос, зачем backend-разработчику Camunda. Написала про мониторинг бизнес-процессов в Camunda 8. В новой статье хочу призвать вас активно тестировать процессы в Camunda и на понятном примере показать, как это устроено. Ладно, когда у вас пара воркеров, а если это большой процесс с разветвлениями и разными вариантами развития логики? А так написал чуть больше сотни тестов и сразу на душе легче стало. Не шучу, на один из процессов я разработала их почти 150.
Ошибки в бизнес-процессах
Для того, чтобы продемонстрировать, как тестировать процессы, давайте создадим новую диаграмму, показывающую daily routine перед работой.
![](https://habrastorage.org/getpro/habr/upload_files/9f8/6fd/375/9f86fd375134ced055b33ef4ae1da666.jpg)
Что будет, если не удается проснуться, поесть или раздать корм домашним животным? Ошибка, которая ведет к завершению процесса – уведомляется PM, мол, простите-извините, работать не получится.
Если не удается выпить чашечку кофе, это печально, но не критично для процесса. Выбрасываем error boundary event, чтобы в дальнейшем было видно, что в процессе произошла внештатная ситуация, и завершаем процесс. Напоминаю, про элементы BPMN схемы можно почитать здесь.
![](https://habrastorage.org/getpro/habr/upload_files/2bb/4af/67a/2bb4af67a5925575900c8f8a66bf9be5.png)
Код вы можете увидеть в моем гите, а сейчас посмотрим этот коммит. Замечу, в примере используется 21 Java, Spring Boot версии 3.3.6, а также spring-zeebe-starter версии 8.4.12. Для тестирования необходимо подключить зависимость:
<dependency>
<groupId>io.camunda</groupId>
<artifactId>spring-zeebe-test</artifactId>
<version>8.4.12</version>
<scope>test</scope>
</dependency>
Небольшое отступление: хотела изначально взять зависимость поновее, однако в консоли начал спамить лог The request's security level does not guarantee that the credentials will be confidential. С ходу разобраться не удалось, поэтому остановилась на версии 8.4.12, если вдруг кто сталкивался и победил, расскажите, пожалуйста, в комментариях.
Также обновила докер компоуз, чтобы вы локально могли все запустить и прогнать процесс. Внимание, elasticsearch просто так не качается, если вы понимаете о чем я.
Использование воркеров
Объяснять, что такое воркер, сервисные таски и прочее не буду. Если вы преисполнились настолько, что решили написать тесты на ваш процесс, то вы явно понимаете, как что работает. Рекомендую использовать autoComplete = false – самим управлять завершением воркера. Это позволяет придать коду большую гибкость. Мы в работе используем реактив, и в зависимости от того, возвращается next signal или error signal, выполняем различные действия.
![](https://habrastorage.org/getpro/habr/upload_files/d1f/078/476/d1f078476af6c9b3317a58f5cc1cd81e.png)
Что происходит в статическом методе sendZeebeCompleteCommand, можно увидеть в примере кода, приведенном ниже. Полный класс с вспомогательными методами можно посмотреть в гите.
public static void sendZeebeCompleteCommand(final JobClient client, long jobKey, ZeebeVariable variable) {
client.newCompleteCommand(jobKey)
.variables(variable)
.send()
.exceptionally(t -> {
log.error(ERROR_EXECUTE_COMMAND, t.getMessage());
throw new ZeebeBpmnError(COMMAND_EXECUTION_EXCEPTION_CODE, COMPLETE_COMMAND_EXCEPTION_MESSAGE, null);
});
}
В учебном проекте используются методы сервиса GodService, которые вызываются в воркерах.
![](https://habrastorage.org/getpro/habr/upload_files/4dd/0c7/8d1/4dd0c78d1be2feac28384b6a389fb3c5.jpg)
Предположим, для PM важно знать, на каком шаге произошла критическая ошибка. Для этого введены специальные коды. Например, если не удалось проснуться, то будет код 1.
![](https://habrastorage.org/getpro/habr/upload_files/4fd/740/023/4fd74002378f87e856f0821867ded604.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/7a8/25d/1ce/7a825d1ce177547adbeb03515e2f58af.jpg)
MessageSender – вспомогательный класс, который позволяет избежать дублирования кода, ведь по сути в трех воркерах при наступлении критической ошибки происходит одна и та же логика. Мы сохраняем variables в ApologyMessageData и выбрасываем Error Start Event, которое является триггером для выполнения воркера “Отправить сообщение руководителю проекта”.
![](https://habrastorage.org/getpro/habr/upload_files/874/0a7/f6e/8740a7f6e7ca08e03944b2d0c1547e1f.png)
И уже непосредственно в воркере “Отправить сообщение руководителю проекта” можно увидеть, какой apologyCode пришел.
![](https://habrastorage.org/getpro/habr/upload_files/dac/e48/335/dace4833595d97d86ff023a24cb538aa.jpg)
Приступим к тестированию процессов
![](https://habrastorage.org/getpro/habr/upload_files/506/954/594/506954594784b3f3deb49e6ddf7ee7e7.jpg)
Над классом с тестами необходимо навесить две аннотации, а также заавтовайрить ZeebeClient. Далее мокаем все сервисы, которые используются в воркерах. Напоминаю, задача состоит в том, чтобы протестировать процесс. То что, происходит в сервисах, остается в сервисах, для этого используются другие тесты.
Так как у нас только один божественный сервис, то, естественно, мокаем только его. В константах используются айдишники элементов, напоминаю, где их можно посмотреть.
![](https://habrastorage.org/getpro/habr/upload_files/949/dbc/7b2/949dbc7b2cd613bb06a1567c458b1c13.jpg)
У элемента с типом enjoyCupOfCoffeeTask - ID Activity_08x2kch, а у error boundary event - ID Event_077d3l.
Далее перед запуском каждого теста необходимо запустить все моки.
@BeforeEach
void prepareMocks() {
when(godService.hasPet()).thenReturn(true);
doNothing().when(godService).getUp();
doNothing().when(godService).haveBreakfast();
doNothing().when(godService).feedPet();
doNothing().when(godService).enjoyCupOfCoffeeTask();
}
То есть описывается поведение всех методов, которые вызываются в классах-воркерах.
Начнем с happy path. Мы проснулись, поели, покормили питомца и выпили чашечку кофе. Следовательно, элемент с отправкой сообщения PM не отработал.
![](https://habrastorage.org/getpro/habr/upload_files/8a7/37f/3f3/8a737f3f3432c70383c782bde372517d.png)
Ниже приведу код методов runProcessInstance() и waitForPassingElement (long processInstanceKey, String elementId)
private ProcessInstanceEvent runProcessInstance() {
return client.newCreateInstanceCommand()
.bpmnProcessId(PROCESS_NAME)
.latestVersion()
.send()
.join();
}
private void waitForPassingElement(long processInstanceKey, String elementId) {
// ждём выполнения воркера 60 сек
waitForProcessInstanceHasPassedElement(processInstanceKey, elementId, Duration.ofSeconds(60));
}
Поясняю, что происходит в методе теста
Сначала мы ждем выполнения элементов BPMN схемы:
waitForPassingElement(processInstanceKey, GET_UP_TASK);
waitForPassingElement(processInstanceKey, HAVE_BREAKFAST_TASK);
waitForPassingElement(processInstanceKey, FEED_PET_TASK);
waitForPassingElement(processInstanceKey, ENJOY_CUP_OF_COFFEE_TASK);
Затем смотрим, что у нас есть отработавшие элементы схемы, да еще и в нужном порядке.
.hasPassedElementsInOrder(
GET_UP_TASK,
HAVE_BREAKFAST_TASK,
FEED_PET_TASK,
ENJOY_CUP_OF_COFFEE_TASK
)
Также с помощью теста мы проверяем, что процесс стартовал:
.isStarted()
В нем не было инцидентов:
.hasNoIncidents()
И он успешно завершен:
.isCompleted();
Еще необходимо проверить, что не отработали элементы error boundary event, error start event и service task с типом sendMessageToProjectManagerTask:
.hasNotPassedElement(ZEEBE_ERROR_ENJOY_CUP_OF_COFFEE)
.hasNotPassedElement(ZEEBE_ERROR_SEND_MESSAGE_TO_PM_TASK)
.hasNotPassedElement(SEND_MESSAGE_TO_PM_TASK)
Так как в процессе отработал элемент “Покормить домашнее животное”, значит переменная hasPet имела значение true. Это также проверяется в методе:
.hasVariableWithValue(VARIABLE_HAS_PET, true)
Вишенка на торте – проверка, что методы сервиса действительно отработали:
assertAll(
() -> verify(godService, times(1)).getUp(),
() -> verify(godService, times(1)).haveBreakfast(),
() -> verify(godService, times(1)).feedPet(),
() -> verify(godService, times(1)).enjoyCupOfCoffeeTask()
);
Теперь давайте протестируем вариант, который предусматривает, что произошла ошибка во время выполнения элемента “Поесть”. Как мы помним, процесс прерывает нормальное выполнение, отрабатывают элементы error start even и service task с типом sendMessageToProjectManagerTask.
![](https://habrastorage.org/getpro/habr/upload_files/1a3/434/9af/1a34349af8333d51edb6791ffc369abc.jpg)
Чтобы элемент “Поесть” не отработал, надо выбросить ошибку во время выполнения метода haveBreakfast(). Если подниметесь в начало статьи, то можно ещё раз для наглядности посмотреть код воркера haveBreakfastTask.
Итак, снова ждем выполнения нужных элементов:
waitForPassingElement(processInstanceKey, GET_UP_TASK);
waitForPassingElement(processInstanceKey, ZEEBE_ERROR_SEND_MESSAGE_TO_PM_TASK);
waitForPassingElement(processInstanceKey, SEND_MESSAGE_TO_PM_TASK);
Проверяем, что запланированные элементы действительно отработали:
.hasPassedElement(GET_UP_TASK)
.hasPassedElement(ZEEBE_ERROR_SEND_MESSAGE_TO_PM_TASK)
.hasPassedElement(SEND_MESSAGE_TO_PM_TASK)
Обратите внимание, элемент “Поесть” НЕ отработал, то есть он начал свою работу – стал ACTIVATED, но в статус COMPLETED не перешел. Соответственно, также не отработали элементы “Покормить домашнее животное” и “Выпить чашечку кофе”:
.hasNotPassedElement(HAVE_BREAKFAST_TASK)
.hasNotPassedElement(FEED_PET_TASK)
.hasNotPassedElement(ENJOY_CUP_OF_COFFEE_TASK)
.hasNotPassedElement(ZEEBE_ERROR_ENJOY_CUP_OF_COFFEE)
Процесс стартовал:
.isStarted()
В нем не было инцидентов:
.hasNoIncidents()
И он успешно завершен:
.isCompleted();
Проверяем значение переменной apologyCode:
.hasVariableWithValue(VARIABLE_APOLOGY_CODE,
ApologyCode.COULD_NOT_HAVE_BREAKFAST.getCode())
И куда без проверки срабатывания методов сервиса:
assertAll(
() -> verify(godService, times(1)).getUp(),
() -> verify(godService, times(1)).haveBreakfast(),
() -> verify(godService, never()).feedPet(),
() -> verify(godService, never()).enjoyCupOfCoffeeTask()
);
Также примеры тестов можно посмотреть в гите комьюнити камунды. У меня все. Ещё раз призываю писать тесты на процессы, чтобы потом не было вот так.
![](https://habrastorage.org/getpro/habr/upload_files/a92/58d/7d3/a9258d7d3c49439830348a8c5010e98d.jpg)
Всем спасибо. Пишите свои мысли в комментариях – обсудим.
qountike
Я человек простой, вижу статью Вероники - ставлю лайк
ververesk Автор
Я человек простой, вижу такой комментарий, понимаю, что не зря писала))))