itmo_conspects

Лекция 6. Тестирование переходов состояний

Если система имеет несколько состояний, между которыми она может переходить, то она требует отдельного подхода

Одно и то же событие может приводить к разным результатам в зависимости от исходного состояния объекта и другого контекста. Например, нажатие на кнопку “Отмена” в состоянии “Отправлено” в системе отправки опросов может давать мгновенный переход, а в состоянии “Принято” - запускать дополнительные проверки

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

Поэтому важно понимать связи между состояниями, события, которые создают переходы, и условия переходов


Такую систему можно смоделировать с помощью конечного автомата. Детерминированный конечный автомат (Finite State Machine, FSM) $A = (S, E, T, s_0, F)$ - это структура, состоящая из:

Автомат можно описать с помощью ориентированного графа или матрицы состояний и переходов

Если появляется новое состояние, то его добавление в модель определяется его влиянием на логику переходов, событий или на изменение инварианта

Сам переход состоит из нескольких элементов:

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


В реальных системах автомат состояний может быть сложнее, например, из-за дополнительных условий или глобального состояния. Поэтому определение автомата расширяется до расширенного автомата (Extended Finite State Machine, EFSM) - в нем переход зависит не только от состояния и триггера, но и от дополнительных данных, например флагов. Например, событие “Отменить” в состоянии “Оплачен” допустимо, только если не установлен флаг “Передан в доставку”. Важно помнить, что слишком подробная модель может привести к избыточной детализации и повышению числа комбинаций, а слишком грубая может пропустить важные проверки

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

Полнота модели проверяется через матрицу состояний и событий, которая указывает следующее состояние по переходу из данного, например:

Состояние \ Событие Оплатить Отменить Доставить Создать
✅ Заказ создан 💵 Заказ оплачен ❌ Заказ отменен Не определено Не определено
💵 Заказ оплачен Состояние не меняется Не определено 📦 Заказ доставлен Отказ
📦 Заказ доставлен Отказ Не определено Состояние не меняется Отказ
❌ Заказ отменен Отказ Состояние не меняется Отказ ✅ Заказ создан

В такой матрице видно, какие переходы определены, а какие нет, поэтому легко выявляются возможные проблемы:

Для сохранения детерминизма результат должен быть уникальным для заданной комбинации состояния, события и данных

Если есть несколько возможных переходов, удовлетворяющих условию, то возникает конфликт. Такие конфликты решаются через взаимоисключающие защитные условия или через приоритеты условий

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

Пример графа машины состояний

Также важно учитывать инварианты - свойства, которые не должны меняться после определенного события. Например, при оплате идентификатор платежа не должен меняться, поэтому в ходе проверки важно проверять, что он остается постоянным


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

Преобразование модели в тесты делается так:

Например, для модели выше можем получить тест-кейс с предусловием “Начальное состояние - ‘Заказ создан’”, действием - “Совершить событие ‘Оплатить’” и ожидаемым результатом “Новое состояние - ‘Заказ оплачен’”

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

Покрытие тестов можно проверять по критериям:

Чем больше $N$, тем строже критерий покрытия и больше тестов. Число $N$ выбирается исходя из рисков


В итоге, этапы работы выглядят так:

  1. Сбор информации из требований или других источников
  2. Построение модели
  3. Анализ модели (поиск тупиков и избыточности)
  4. Обновление модели
  5. Генерация тест-кейсов
  6. Оценка покрытия
  7. Выполнение тестов