Тестирование процессов
Глава 7 показала, что процесс это место, где живёт архитектура: стрелки на схеме выводятся из шагов Caller > Callee. Процесс также ветвится: if по значению, try, который может завершиться неудачей, select, который запускает некоторые кейсы, await, который может истечь по таймауту. Каждое из них это решение, которое диаграмма оставляет открытым. Тест закрывает их: фиксирует решения, а затем проверяет получившуюся трассу.
Ключевая идея: тест процесса это mock по построению. Он ничего не исполняет и не касается бизнес-данных. Он проверяет форму результата: какие шаги выполнились, в каком порядке, чем завершился поток, какие компенсации откатили изменения. Это именно тот слой, который диаграмма может незаметно сломать, и именно то, что статическая модель может честно проверить.
Конструкция test
Заголовок раздела «Конструкция test»Тест задаёт имя процесса, фиксирует его точки выбора в блоке when и проверяет трассу в блоке then:
process #chk Checkout { Orders if "high value": > Shipping.express else: > Shipping.standard Orders try { > Payments.charge } catch declined { > Channels.notify } Orders select one "channel" { sms: > Channels.sms email: > Channels.email } Orders > Orders.place}
test "high-value order, card charged, emailed" of Checkout { when { "high value": yes "try": pass channel: [email] } then { Shipping.express Payments.charge Channels.email Orders.place Channels.sms never }}Заголовок имеет вид test [#id] "<name>" [of <Process>]. Тест объявляется на верхнем уровне файла, внутри тела модуля или как in <Module> test … - та же грамматика размещения, что и у view и policy. test, when и then это контекстные ключевые слова: модуль или шаг с именем test всё равно разбирается; ключевое слово связывается только в своей позиции.
Фиксация в when
Заголовок раздела «Фиксация в when»Каждая фиксация отвечает на одну точку выбора и использует в качестве ключа то, что уже названо в модели:
| Точка выбора | Фиксация | Значение |
|---|---|---|
if "<cond>": | "<cond>": yes / no | взять (или пропустить) эту ветку |
успех try { } | "try": pass | защищённые шаги завершаются успешно |
| шаг с ошибкой | <Callee.step>: fails или fails "cause" | внедрить сбой (переводит в подходящий catch или завершает процесс с ошибкой) |
| цикл повторов | <Callee.step>: fails "x", ok | последовательность результатов по попыткам |
select "<head>" | <head>: [caseA, caseB] | какие кейсы выполнились и в каком порядке |
| задержка шага | <Callee.step>: after <dur> / takes <dur> | бизнес-время / системное время на шаге |
граница await … as <label> | <label>: within / elapses | событие приходит вовремя или срабатывает таймаут |
Зафиксируйте достаточно, чтобы свести процесс к одной трассе. Если оставить точку выбора незафиксированной, тест охватит сразу много трасс - инструмент предупредит (under-constrains the process), и его вердикт станет размытым. Добавляйте фиксации, пока предупреждение не исчезнет.
Проверки в then
Заголовок раздела «Проверки в then»Каждая проверка это ссылка на шаг с необязательным модальным хвостом:
happens(по умолчанию: ссылка без суффикса читается какhappens): шаг происходит на каждой трассе. Можно добавить количество:Payments.charge happens 2,happens >= 1.never: шаг не происходит ни на одной трассе.sometimes: шаг происходит хотя бы на одной трассе - свидетель, полезный, когда фиксации всё ещё оставляют альтернативы.fail ["cause"]/finish ["cause"]: трасса завершается этим терминатором (all= каждая живая ветка).time (system|business) <op> <dur>: бюджет задержки по трассе (см. Главу 34).parallel { … }: группа без порядка, каждый элемент происходит в любом порядке.
Порядок задаётся позицией и подразумевает occurrence. Если перечислены A, затем B, это утверждает, что оба происходят и что A предшествует B. unwind в saga проверяется как обычное occurrence: обратный порядок читается как порядок отката.
Покрытие это честная метрика
Заголовок раздела «Покрытие это честная метрика»Запустите набор через CLI:
archlang test --coverageОн выводит для каждого теста PASS / FAIL / OPEN (OPEN = фиксации оставили цикл или множество трасс неограниченным, это никогда не тихий успех) и сводку покрытия точек выбора: долю решений branch / select / failure / timeout в процессе, которые зафиксированы хотя бы одним тестом. Зелёный набор при покрытии только половины точек выбора означает, что процесс протестирован недостаточно, и число это показывает - доверяйте ему больше, чем числу успешных тестов. Удалите тест, и покрытие упадёт, точно показывая, что осталось непроверенным.
Управление через тесты
Заголовок раздела «Управление через тесты»Два selector aspect превращают тесты в gate (см. Главу 32):
@tested- у процесса есть хотя бы один успешный тест.@testCoverage <op> <n>- его покрытие точек выбора проходит заданный порог.
Используйте их в policy, чтобы недостаточно протестированный процесс проваливал проверку, а не уходил в поставку незамеченным. Обязательство require имеет вид <subject>: <obligation>, где this привязан к каждому совпавшему элементу (Глава 32), поэтому булева проверка на самом субъекте записывается как this and where (…):
policy CriticalFlowsTested { require process and where (@@critical): this and where (@tested and @testCoverage >= 80)}Тест, привязанный через #id, также управляет regression gate: archlang change-check сравнивает base и head, и тест, который проходил на base, но падает на head, будет показан в diff как регрессия.
Захват теста из scrubber
Заголовок раздела «Захват теста из scrubber»Самый быстрый способ написать тест это не печатать его. Пройдите процесс в flow view, отвечая на его выборы по ходу: выберите ветку, внедрите сбой на шаге, укажите, какие кейсы select выполняются, задайте задержку, затем нажмите Save as test. Сеанс с данными ответами будет сохранён как блок test, добавленный в файл процесса, и повторный запуск воспроизведёт в точности ту трассу, которую вы прошли в scrubber. Считайте этот захват отправной точкой: переименуйте его так, чтобы было ясно, что именно он проверяет, и сократите проверки до тех, которые фиксируют важный для вас результат.
Что тестировать
Заголовок раздела «Что тестировать»- Тестируйте форму, а не данные. Проверяйте шаги, порядок, терминаторы, компенсации. Бизнес-значений в модели нет; тест, который пытается до них дотянуться, проверяет не тот слой.
- Покрывайте ветки с ошибками. Переход
failsвcatch, откат saga,await, которыйelapses- вот что диаграмма может незаметно сломать при правке. Счастливый путь ломается редко. - Одна трасса на тест. Ограничивайте
when, пока не исчезнет предупреждение о недостаточной фиксации; размытый тест даёт размытые контрпримеры.