Перейти к содержимому

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

Глава 7 показала, что процесс это место, где живёт архитектура: стрелки на схеме выводятся из шагов Caller > Callee. Процесс также ветвится: if по значению, try, который может завершиться неудачей, select, который запускает некоторые кейсы, await, который может истечь по таймауту. Каждое из них это решение, которое диаграмма оставляет открытым. Тест закрывает их: фиксирует решения, а затем проверяет получившуюся трассу.

Ключевая идея: тест процесса это mock по построению. Он ничего не исполняет и не касается бизнес-данных. Он проверяет форму результата: какие шаги выполнились, в каком порядке, чем завершился поток, какие компенсации откатили изменения. Это именно тот слой, который диаграмма может незаметно сломать, и именно то, что статическая модель может честно проверить.

Тест задаёт имя процесса, фиксирует его точки выбора в блоке 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 всё равно разбирается; ключевое слово связывается только в своей позиции.

Каждая фиксация отвечает на одну точку выбора и использует в качестве ключа то, что уже названо в модели:

Точка выбораФиксацияЗначение
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), и его вердикт станет размытым. Добавляйте фиксации, пока предупреждение не исчезнет.

Каждая проверка это ссылка на шаг с необязательным модальным хвостом:

  • 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 как регрессия.

Самый быстрый способ написать тест это не печатать его. Пройдите процесс в flow view, отвечая на его выборы по ходу: выберите ветку, внедрите сбой на шаге, укажите, какие кейсы select выполняются, задайте задержку, затем нажмите Save as test. Сеанс с данными ответами будет сохранён как блок test, добавленный в файл процесса, и повторный запуск воспроизведёт в точности ту трассу, которую вы прошли в scrubber. Считайте этот захват отправной точкой: переименуйте его так, чтобы было ясно, что именно он проверяет, и сократите проверки до тех, которые фиксируют важный для вас результат.

  • Тестируйте форму, а не данные. Проверяйте шаги, порядок, терминаторы, компенсации. Бизнес-значений в модели нет; тест, который пытается до них дотянуться, проверяет не тот слой.
  • Покрывайте ветки с ошибками. Переход fails в catch, откат saga, await, который elapses - вот что диаграмма может незаметно сломать при правке. Счастливый путь ломается редко.
  • Одна трасса на тест. Ограничивайте when, пока не исчезнет предупреждение о недостаточной фиксации; размытый тест даёт размытые контрпримеры.