Привет, Хабр!

Меня зовут Андрей Бирюков. Я — независимый эксперт в области ИТ и ИБ, преподаю в учебных центрах и пишу статьи и книги.

У вас есть проект, которому больше года. В папке «Тесты» лежат 847 файлов с Gherkin‑сценариями. В CI/CD они прогоняются, зелёные лампочки горят. Вы чувствуете себя уверенно — охват хороший, всё покрыто. А потом приходит новый разработчик. Он меняет логику статуса заказа, но при этом все 847 тестов проходят.

Однако, через неделю в проде выясняется, что сломался сценарий, которого в тестах не было. Потому что никто не знал, что этот сценарий вообще возможен. Или он был, но его удалили «для чистоты». Или он лежит в другом репозитории, и его забыли обновить.

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

И здесь проблема заключается не в качестве тестирования, а в используемом методе. Мы пишем сценарии как изолированные истории, но система — это конечный автомат. И если мы не моделируем автомат, мы никогда не узнаем, какие переходы остались без покрытия, а какие сценарии дублируют друг друга под разными названиями.

Как 200 сценариев не покрыли 4 перехода

Допустим у нас был проект — система управления заявками в техподдержке. Сущность «Заявка» имела 6 статусов:

  1. Новая.

  2. В работе.

  3. На согласовании.

  4. Готова к тестированию.

  5. Тестируется.

  6. Закрыта.

Команда написала 200 сценариев на Gherkin, проверяла их в каждом спринте, все были зелёные.

В один прекрасный день пришло требование: добавить автоматическую эскалацию, если заявка висит в статусе «На согласовании» больше двух дней. Разработчик добавил фоновый джоб, который переводит заявку из «На согласовании» в «В работе» по таймауту.

Тесты упали, — потому что ни один из 200 сценариев не предусматривал переход «На согласовании» → «В работе» в обратную сторону. Раньше этот переход был только прямой (из «В работе» в «На согласовании»).

Команда добавила один сценарий для нового перехода. Тесты позеленели. Через неделю выяснилось, что при возврате на «В работе» система не сбрасывает поля, которые должны быть заполнены только на этапе согласования. В проде появились заявки с пустыми сроками решения. Потому что разработчик добавил переход, но не проверил инварианты состояния.

Проблема заключалась в том, что никто не знал полную модель переходов между статусами. 200 сценариев описывали отдельные истории: «Пользователь создаёт заявку», «Менеджер берёт в работу», «Руководитель согласовывает». Каждый сценарий был изолирован. Никто не свёл их в единую карту состояний и не проверил, какие переходы вообще возможны, а какие — нет.

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

  • Новая → В работе (менеджер назначает исполнителя).

  • В работе → На согласовании (заявка готова, нужно подтверждение).

  • На согласовании → В работе (согласование отклонено, доработка).

  • На согласовании → Готова к тестированию (согласовано).

  • На согласовании → В работе (таймаут 2 дня — эскалация).

  • Готова к тестированию → Тестируется (тестировщик начал проверку).

  • Тестируется → Готова к тестированию (найдены баги).

  • Тестируется → Закрыта (тестирование успешно).

  • Любой статус → Закрыта (администратор принудительно).

Всего 9 переходов. А теперь вопрос: сколько из 200 сценариев проверяют переход «На согласовании» → «В работе» (таймаут)? Оказалось, ноль. Потому что этот переход появился позже, и никто не подумал посмотреть на модель и добавить сценарий для отсутствующего перехода.

Когда мы перестроили подход, мы перестали писать сценарии как истории. Мы начали писать их как проверки переходов. Каждый сценарий привязывался к конкретному переходу на карте состояний. И у нас появилась простая метрика: из 9 возможных переходов покрыто 9. Или 8, и мы знаем, какой именно не покрыт, и это запланировано в следующем спринте.

В той же системе мы пошли дальше и построили матрицу покрытия состояний. Это была таблица, где строки — текущие состояния заявки, столбцы — действия пользователя или события системы. На пересечении стояла отметка: разрешён ли этот переход, и есть ли для него сценарий.

Для статуса «На согласовании» матрица выглядела так:

  • Действие «Утвердить» → переход в «Готова к тестированию» → сценарий есть.

  • Действие «Отклонить» → переход в «В работе» → сценарий есть.

  • Событие «Таймаут 2 дня» → переход в «В работе» → сценария нет (красная ячейка).

  • Действие «Перевести в тестирование» → запрещено (недоступно в UI).

  • Действие «Закрыть заявку» → запрещено (только администратор).

Когда мы увидели красную ячейку для таймаута, мы сразу поняли, где проблема. Но что ещё важнее — мы увидели, что сценарий для таймаута не просто отсутствует. Мы поняли, что сам переход не был описан в требованиях. Бизнес сказал «добавьте эскалацию», но не уточнил, что должно происходить с данными при возврате в «В работе». Разработчик догадался, как реализовать, но его догадка оказалась неполной.

Если бы мы строили матрицу до начала разработки, мы бы задали бизнесу вопрос: «При возврате из „На согласовании“ в „В работу“ по таймауту, какие поля должны сохраниться, а какие — сброситься? Нужно ли уведомлять автора заявки? Должен ли измениться приоритет?» Это были бы вопросы на этапе анализа, а не после того, как тесты упали, а прод упал ещё раньше.

Сценарий‑конфликт как обязательный паттерн

Ещё одна проблема с традиционным Specification by Example — мы пишем сценарии для «счастливого пути» и для типичных ошибок. Но мы почти никогда не пишем сценарии для конфликтных ситуаций, когда два процесса пытаются изменить одну сущность одновременно.

Вот пример все из той же системы заявок.

  • Сценарий А: «Менеджер берёт заявку в работу».

  • Сценарий Б: «Автор заявки отменяет заявку».

Каждый из них по отдельности проверен. Но что будет, если менеджер и автор нажмут свои кнопки одновременно? Кто победит? Какое состояние станет финальным?

В классическом подходе это «редкий случай», им можно пренебречь. Но в распределённой системе это не редкий случай. Это гарантированный случай, который произойдёт при первом же нагрузочном тестировании или при первом же нерадивом пользователе, который кликает дважды.

Как мы решили это в проекте? Мы ввели понятие обязательного сценария‑конфликта для каждого перехода, который может быть инициирован из разных мест.

Для заявки это были:

  1. Переход «Новая → В работе» может быть инициирован менеджером (вручную) и автоматическим диспетчером (при распределении). Сценарий‑конфликт: менеджер взял заявку, и одновременно диспетчер назначил её другому менеджеру. Кто победит? Мы записали правило: побеждает ручное действие, второй менеджер получает уведомление о конфликте, заявка остаётся у первого.

  2. Переход «В работе → Закрыта» может быть инициирован исполнителем (завершил работу) и администратором (принудительно). Сценарий‑конфликт: исполнитель нажал «Закрыть», администратор нажал «Отменить» в тот же момент. Правило: администратор всегда побеждает, но исполнитель получает уведомление, что его закрытие отменено.

  3. Переход «На согласовании → Готова к тестированию» может быть инициирован согласующим и автоматически по истечении срока (если включена автоподпись). Сценарий‑конфликт: согласующий отклонил, но автоподпись сработала на секунду позже. Правило: отклонение всегда побеждает автоподпись, потому что автоподпись — это fallback, а не основной сценарий.

Когда мы добавили эти сценарии‑конфликты в набор требований, разработчики перестали думать «а вдруг». Они начали проектировать блокировки, версионирование записей (optimistic locking) и явные политики разрешения конфликтов. И это было спроектировано до того, как первый пользователь нажал две кнопки одновременно.

Как превратить сценарии в систему: Пошаговый алгоритм

Если вы хотите перейти от списка сценариев к системному покрытию, вот алгоритм, который работает:

Шаг 1. Постройте модель состояний сущности. Не для всех сущностей сразу, а для одной, самой критичной. Нарисуйте все статусы, которые она может принимать. Добавьте все возможные переходы между статусами. Не пропускайте «редкие» и «маловероятные». Если переход технически возможен, он должен быть на карте.

Шаг 2. Для каждого перехода определите триггер. Кто или что инициирует этот переход? Пользователь (какая роль)? Автоматическое событие (таймаут, ответ внешней системы)? Администратор вручную? Запишите все это явно.

Шаг 3. Назначьте каждому переходу приоритет покрытия. Критические переходы (связанные с деньгами, юридическими обязательствами, данными клиентов) должны иметь несколько сценариев: успешный, с ошибкой, с конфликтом. Второстепенные переходы могут иметь один happy path.

Шаг 4. Напишите сценарии не как истории, а как проверки переходов. Формат может быть тот же Gherkin, но структура меняется. Вместо формулировки «Как пользователь, я хочу...» используйте конструкцию: «При условии, что заявка находится в статусе X, когда происходит событие Y, тогда заявка должна перейти в статус Z, и при этом должны быть соблюдены все инварианты системы...»

Шаг 5. Ведите матрицу покрытия. Простая таблица: переход → есть сценарий? → есть сценарий‑конфликт? → есть негативный сценарий? Когда приходит новое требование, вы смотрите на матрицу и понимаете: добавился новый переход — мы должны добавить сценарии. Изменился существующий переход — мы должны обновить сценарии. Список из 847 файлов вам не скажет, что изменилось. Матрица скажет.

Если вы работаете с требованиями, моделями и изменениями систем, полезно проверить, насколько ваши знания соответствуют задачам системного аналитика экспертного уровня. Пройдите вступительный тест — он поможет увидеть сильные стороны и найти пробелы для дальнейшего развития.

А что делать с 847 существующими сценариями?

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

  2. Выберите самую важную сущность — ту, где больше всего багов в проде.

  3. Постройте для неё модель состояний (это займёт один день с разработчиками).

  4. Возьмите все существующие сценарии для этой сущности и отметьте, какой переход они покрывают.

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

  6. Посмотрите, какие переходы остались без покрытия. Напишите для них по одному базовому сценарию.

В следующих спринтах, когда будете добавлять новые сценарии для новых фич, сразу вносите их в матрицу, а не в бесконечную папку.

Через три месяца у вас будет не 847 файлов, а 40–50 сценариев на сущность, но каждый из них будет значимым, и вы будете точно знать, что все переходы покрыты. И когда придёт новый разработчик, вы покажете ему не папку с тестами, а карту состояний и матрицу покрытия. Он увидит систему, а не список.

Главный вывод

Specification by Example без системной модели — это просто коллекция историй. Она создаёт иллюзию уверенности, но не даёт реального контроля над качеством. Когда вы не знаете, какие переходы возможны, вы не знаете, какие из них протестированы. И каждый релиз становится лотереей.

Строить модель состояний — это не сложно. Это пять‑шесть статусов, десять‑пятнадцать переходов. Это не займёт много времени. Но это требует дисциплины: не перескакивать к написанию сценариев, пока не нарисована карта. И не добавлять новые переходы в код, пока они не появились на карте и в матрице покрытия.

Когда вы перейдёте на этот подход, вы перестанете быть «кукловодом», который дёргает за верёвочки 847 тестов и надеется, что они не запутаются. Вы станете архитектором тестового покрытия, который точно знает, что система проверена по всем осям — по всем состояниям, по всем переходам, по всем конфликтам.

И тогда слово «регрессия» перестанет быть страшным. Потому что вы будете знать, что изменилось: добавился новый переход? Проверяем его. Изменился существующий? Проверяем его и его соседей по матрице. Всё остальное остаётся зелёным не потому, что мы верим, а потому, что мы знаем — эти переходы не изменились, и их сценарии по‑прежнему валидны.

Это и есть системный анализ в чистом виде: не сбор требований, а проектирование проверяемой реальности.

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

На бесплатных уроках по системному анализу разберём, как превращать бизнес‑задачи в понятные модели: от пользовательских сценариев и управления данными до анализа изменений архитектуры с помощью BACCM, (4+1) и C4. Это поможет выстроить более предсказуемый процесс работы между аналитиком, разработчиком и бизнесом.

  • 6 августа, 20:00. «Пользовательские сценарии (Use Cases) на реальном примере: от бизнес‑требования заказчика до формулирования задачи для разработчика». Записаться

  • 13 августа, 20:00. «Управление данными в MSA — дыра в бюджете или актив для ИИ‑трансформации?». Записаться

  • 20 августа, 20:00. «Аналитическая дженга: как проектировать и управлять изменениями системы с помощью BACCM, (4+1) и C4, чтобы архитектура не рассыпалась». Записаться

Комментарии (0)