Имея некоторый опыт в тестировании, обнаружила, что аналитики и программисты часто не уделяют внимание некорректным переходам между состояниями сущностей. Что это значит на практике? Например, вы можете удалить то, что уже было удалено, отредактировать уже отправленное и т.д. Такого рода действия могут привести к необработанным исключениям, в том числе ошибкам на уровне БД. Почему надо обрабатывать такие ошибки? Как минимум, плохо показывать пользователю информацию о логической структуре БД. Такие ошибки чаще всего свойственны многопользовательским системам, где несколько пользователей могут редактировать один и тот же объект. В этой статье я расскажу, как проектировать тесты для проверки переходов между состояниями объекта и как быстро оценивать затраты на такое тестирование.
В большинстве случаев требований к состояниям сущности в явном виде нет, и тестировщику приходится формулировать их самому, выделяя нужную информацию из сценариев использования / user stories. Если же требования все-таки есть, они выглядят как-то так:
Прекрасно, если у вас есть подобные диаграммы. Но если нет, тоже ничего страшного, их можно построить самостоятельно по имеющейся информации о возможных операциях над объектом.
Требование 1: сразу после получения сообщений с почтового сервера они помечены как непрочитанные.
Требование 2: когда пользователь кликает на сообщение, чтобы открылось его содержание, сообщение становится прочитанным.
Требование 3: пользователь может пометить ранее прочитанные сообщения как непрочитанные.
Требование 4: пользователь может удалить сообщения (как прочитанные, так и непрочитанные), вернуть сообщения после удаления нельзя.
По этим требованиям можно получить такую диаграмму для сообщений:
Простые позитивные тесты по диаграммам состояний представляют собой проверку валидных переходов между состояниями, т.е. возможность выполнить действия, обозначенные стрелками. Негативные тесты – проверка невозможности выполнения никаких других действий. Таким образом, легко посчитать количество необходимых позитивных тестов – количество стрелок на диаграмме. Количество негативных тестов тоже несложно подсчитать:
Формулу выше легко получить из матричного представления диаграммы состояний. Такое представление строится следующим образом:
Таким образом, количество плюсов в матрице соответствует позитивным тестам, а пустых ячекк – негативным. Их количество расчитывается по формуле выше.
Самое сложное при тестировании состояний – не подсчитать количество тестов или ничего не забыть (все тесты легко учитываются с помощью матрицы), а спроектировать реальные сценарии, соответствующие негативным тестам.
По такому представлению данных легко дать оценку на тестирование, особенно если собраны метрики времени: среднее время на написание теста и среднее время прохождения – достаточно помножить каждый показатель на N2 и сложить их.
Спроектируем матричное представление для следующей диаграммы состояний:
Таким образом, для этой диаграммы будет четыре простых позитивных теста и пять негативных.
Если проектировать негативные тесты для почтового веб-клиента, они будут выглядеть приблизительно так:
— Не прочитано – Не прочитано:
Ожидаемый результат: сообщение на обоих вкладках помечено как непрочитанное, на Шаге 3 не возникает сообщения об ошибке.
— Удалено – Удалено:
Ожидаемый результат: сообщение на обоих вкладках удалено (пропало из списка сообщений), на Шаге 3 не возникает сообщения об ошибке.
Проектирование такого рода тестов – самая сложная часть, не исключено, что не для всех пустых ячеек таблицы выше удастся придумать сценарии (хотя в данном примере это возможно). Тем не менее, это самый простой способ учесть все тесты для диаграммы состояний.
Тестирование, очевидно, будет состоять из двух стадий:
Простые позитивные тесты представляют собой, как описано выше, проверку одного корректного перехода между двумя состояниями. Таким образом, можно сформулировать простейший критерий покрытия диаграммы тестами: протестированы все существующие переходы между состояниями, и объект побывал в каждом состоянии хотя бы один раз. Но можно проектировать и более сложные сценарные тесты, состоящие из более чем одного перехода. Такие тесты могут быть в дальнейшем использованы и для нагрузочного тестирования. Аналогично, можно задать более сильные критерии покрытия тестами: проверены все возможные пары переходов между состояниями, тройки и т.д. (до N-1, где N – количество состояний объекта). Такое покрытие называется покрытием переходом Чау или покрытием N-1 переходов. Соответственно, самое слабое покрытие будет покрытием 0 переходов.
Применение диаграмм состояний, описанное выше, является не совсем «классическим». В «классическом» виде диаграмма состояний представляет собой возможные состояния, в которых может находиться программа (например, все возможные экраны) и переходы между ними (соответственно, способы попасть из одного экрана на другой). Впрочем, такая диаграмма может быть слишком большой для исчерпывающего тестирования (по любому определению покрытия), поэтому иногда может быть целесообразно объединять несколько состояний в одно и проектировать более высокоуровневые тесты, которые можно будет детализировать при необходимости.
Проектирование тестов может быть автоматизировано, если диаграмма состояний задана с помощью одного из формальных языков спецификаций (VDM, Z, B и др.). Существуют универсальные решения для формального подхода к составлению спецификаций и соответственно тестированию на основе такого рода моделей (uniTESK). Но даже если такие инструменты не применяются, необходимое проектирование тестов вполне можно сделать вручную, если диаграмма состояний не слишком большая.
Требования к состояниям сущности
В большинстве случаев требований к состояниям сущности в явном виде нет, и тестировщику приходится формулировать их самому, выделяя нужную информацию из сценариев использования / user stories. Если же требования все-таки есть, они выглядят как-то так:
Прекрасно, если у вас есть подобные диаграммы. Но если нет, тоже ничего страшного, их можно построить самостоятельно по имеющейся информации о возможных операциях над объектом.
Пример: составление диаграммы состояний по требованиям
Требование 1: сразу после получения сообщений с почтового сервера они помечены как непрочитанные.
Требование 2: когда пользователь кликает на сообщение, чтобы открылось его содержание, сообщение становится прочитанным.
Требование 3: пользователь может пометить ранее прочитанные сообщения как непрочитанные.
Требование 4: пользователь может удалить сообщения (как прочитанные, так и непрочитанные), вернуть сообщения после удаления нельзя.
По этим требованиям можно получить такую диаграмму для сообщений:
Как проектировать тесты по диаграммам состояний
Простые позитивные тесты по диаграммам состояний представляют собой проверку валидных переходов между состояниями, т.е. возможность выполнить действия, обозначенные стрелками. Негативные тесты – проверка невозможности выполнения никаких других действий. Таким образом, легко посчитать количество необходимых позитивных тестов – количество стрелок на диаграмме. Количество негативных тестов тоже несложно подсчитать:
T- = N2-T+,где T- – количество негативных тестов, N – количество состояний, T+ – количество позитивных тестов.
Формулу выше легко получить из матричного представления диаграммы состояний. Такое представление строится следующим образом:
- матрица квадратная, количество строк и столбцов равно количеству состояний на диаграмме;
- строки будут обозначать исходящие стрелки, столбцы – входящие;
- если стрелка выходит из состояния 1 и входит в состояние 2, на пересечении строки 1 и столбца 2 следует поставить +.
Таким образом, количество плюсов в матрице соответствует позитивным тестам, а пустых ячекк – негативным. Их количество расчитывается по формуле выше.
Самое сложное при тестировании состояний – не подсчитать количество тестов или ничего не забыть (все тесты легко учитываются с помощью матрицы), а спроектировать реальные сценарии, соответствующие негативным тестам.
По такому представлению данных легко дать оценку на тестирование, особенно если собраны метрики времени: среднее время на написание теста и среднее время прохождения – достаточно помножить каждый показатель на N2 и сложить их.
Пример: тесты для диаграммы состояний сообщения
Спроектируем матричное представление для следующей диаграммы состояний:
Таким образом, для этой диаграммы будет четыре простых позитивных теста и пять негативных.
Если проектировать негативные тесты для почтового веб-клиента, они будут выглядеть приблизительно так:
— Не прочитано – Не прочитано:
- Открыть список сообщений на двух вкладках браузера.
- Найти прочитанное сообщение и пометить его непрочитанным на первой вкладке.
- Не обновляя вторую вкладку, пометить непрочитанным сообщение, выбранное на Шаге 2.
Ожидаемый результат: сообщение на обоих вкладках помечено как непрочитанное, на Шаге 3 не возникает сообщения об ошибке.
— Удалено – Удалено:
- Открыть список сообщений на двух вкладках браузера.
- Найти произвольное сообщение и удалить его на первой вкладке.
- Не обновляя вторую вкладку, удалить сообщение, выбранное на Шаге 2.
Ожидаемый результат: сообщение на обоих вкладках удалено (пропало из списка сообщений), на Шаге 3 не возникает сообщения об ошибке.
Проектирование такого рода тестов – самая сложная часть, не исключено, что не для всех пустых ячеек таблицы выше удастся придумать сценарии (хотя в данном примере это возможно). Тем не менее, это самый простой способ учесть все тесты для диаграммы состояний.
Тестирование, очевидно, будет состоять из двух стадий:
- проверить, что можно корректно выполнить позитивные тесты (существующие переходы между состояниями);
- убедиться, что нет никаких других переходов (негативные тесты).
Сложные тесты по диаграмме состояний
Простые позитивные тесты представляют собой, как описано выше, проверку одного корректного перехода между двумя состояниями. Таким образом, можно сформулировать простейший критерий покрытия диаграммы тестами: протестированы все существующие переходы между состояниями, и объект побывал в каждом состоянии хотя бы один раз. Но можно проектировать и более сложные сценарные тесты, состоящие из более чем одного перехода. Такие тесты могут быть в дальнейшем использованы и для нагрузочного тестирования. Аналогично, можно задать более сильные критерии покрытия тестами: проверены все возможные пары переходов между состояниями, тройки и т.д. (до N-1, где N – количество состояний объекта). Такое покрытие называется покрытием переходом Чау или покрытием N-1 переходов. Соответственно, самое слабое покрытие будет покрытием 0 переходов.
Другие применения диаграмм состояний
Применение диаграмм состояний, описанное выше, является не совсем «классическим». В «классическом» виде диаграмма состояний представляет собой возможные состояния, в которых может находиться программа (например, все возможные экраны) и переходы между ними (соответственно, способы попасть из одного экрана на другой). Впрочем, такая диаграмма может быть слишком большой для исчерпывающего тестирования (по любому определению покрытия), поэтому иногда может быть целесообразно объединять несколько состояний в одно и проектировать более высокоуровневые тесты, которые можно будет детализировать при необходимости.
Проектирование тестов может быть автоматизировано, если диаграмма состояний задана с помощью одного из формальных языков спецификаций (VDM, Z, B и др.). Существуют универсальные решения для формального подхода к составлению спецификаций и соответственно тестированию на основе такого рода моделей (uniTESK). Но даже если такие инструменты не применяются, необходимое проектирование тестов вполне можно сделать вручную, если диаграмма состояний не слишком большая.
gleb_l
Если мы говорим о недооценке графа состояний именно в многопользовательских системах, то я бы упомянул, что операции проверка-переход должны быть либо атомарными, либо синхронизированными на каком-нибудь синхронизационном примитиве. Последнее при использовании пессимистичного сценария блокировок обычно снижает производительность всей системы, а при оптимистичном — требует проверок как на уровне слоя приложения, так и на уровне БД (с последущей эскалацией «некрасивых» ошибок БД в более красивые исключения слоя приложения)
BTW, на уровне базы существует несколько неблокирующих техник, не использующих явные транзакции, но использующие встроенную атомарность операций изменения данных БД