Автор статьи: Павел Жур; Источник: Статья "Рандомный генератор тестовых случаев «Continuous Database Tests»", ресурс Bugs Catcher Я расскажу о том, как я написал фреймворк, позволяющий тестировать сложную логику нашего приложения. Он позволил мне быстро (за пару часов после месяца писанины) отыскать баги, которые команда тестирования искала бы намного дольше, а некоторые из них, скорее всего, не нашла бы вообще. Рассказ будет состоять из семи коротких частей:
1. Проект: наша ситуация Наш проект — это система управления проектами Birdview Projects. Архитектура довольно обычная: UI (HTML, JavaScript), бизнес-логика (C#), база данных (MS SQL). Нюанс состоит в том, что в базе данных, помимо исходных данных, хранятся еще некоторые вычисляемые на лету значения, которые нужно поддерживать в актуальном состоянии. Значений этих много, и поддержка их написана прямо в базе данных, триггерами. Данный факт делает систему более производительной. Чтобы было ясно, о чем идет речь В системе есть следующие сущности: люди, проекты, задачи, сообщения, компании (средство группировки людей). А также некоторые другие штуки (доступ людей к проектам, назначения пользователей на задачи).А вот вычисляемые таблицы и поля:
Пересчеты осложняются тем, что:
Наша ситуация
Минус нашей ситуации состоял в том, что правильность пересчетов было
невозможно протестировать вручную: сложные бизнес-правила, век на то,
чтобы воспроизвести все случаи и идеальная голова, чтобы заметить все
ошибки. Зато был плюс: после вычисляемых значений логика была достаточно
простой, а сами значения почти всегда подчинялись однозначным правилам,
т.е. их легко можно было проверить. Плюс мы использовали очень просто:
написали хранимую процедуру 2. Тестовый фреймворк Итак, оставалось решить вопрос: как гарантировать, что пересчеты всегда работают верно? О юнит-тестах не могло быть и речи: слишком много случаев. Здесь важно отметить две вещи:
Тогда нами было сделано одно предположение – любой баг в пересчетах можно воспроизвести на небольших объемах данных. Скажем, до 10 проектов, до 30 задач, до 20 пользователей, до 5 компаний, до 20 сообщений. Основная идея была такая: мы можем сгенерировать все возможные комбинации и мы знаем множество всех возможных действий. Если из каждой комбинации мы попробуем совершить каждое возможное для неё действие и не получим ошибок, значит система не имеет дефектов. Если мы получим ошибки, у нас тут же будут шаги для воспроизведения. Но с этой идеей была одна сложность: сгенерировать все возможные комбинации непросто. Тогда мы подумали, что поскольку мы все равно должны будем написать вызовы каждого возможного действия и определять для него все возможные наборы параметров, то уже только этих вызовов достаточно, чтобы сгенерировать все возможные ситуации, в которые база данных может прийти. Итак, если мы будем случайным образом выполнять все возможные операции и следить за тем, чтобы количество сущностей в базе данных всегда оставалось маленьким (достаточным для того, чтобы каждый баг проявился), то оставив систему работать на ночь, по закону больших чисел, мы получим все возможные ситуации и все ошибки. Дело оставалось за тремя вещами:
3. Второй сценарий использования Он был такой: иногда в логике пересчетов в базе данных приходится что-то дописывать или менять. Так вот, очень удобно было бы иметь под рукой средство, которое смогло бы быстро протестировать только потенциально затронутый кусок. Разделив все методы (Actions) на функциональные группы (пометив для каждого, на какие области это действие способно влиять), этого удалось добиться: стало можно запускать тестирование, говоря ему: протестируй мне, допустим, только вычисление бюджетов. Заодно, методы, которые вычисляли множество всех возможных входных параметров для действия (ActionEnterInfos) могли работать в разных режимах: они умели предоставлять множества более развернутые для какого-то определенного сценария. То есть, были, буквально, такие режимы:
4. Третий и четвертый сценарии использования Третьим сценарием было тестирование производительности системы, а четвертым – запуск тестирования в несколько потоков, чтобы выявить дедлоки. И как потом, оказалось тестовый фрэймворк помог нам обнаружить серьезные проблемы именно в этом направлении. 5. Реализация Здесь я немного расскажу про особенности реализации. Итак, для каждого действия, которое можно сделать с базой данных, были написаны два метода:
ActionEnterInfo-метод Это метод, который помогает генерировать множество всех возможных комбинаций параметров для первого (Action) метода. Каждой такой комбинации он ставит в соответствие некий вектор: это сделано, чтобы можно было перебрать все возможные комбинации или же выбрать, например, десять случайных. Размерность этих векторов не ограничена, ее метод выбирает сам.
Итак, этот метод инкапсулирует логику работы с входными параметрами.
Логика эта, прежде чем вернуть результат, обращается к базе данных и
читает из нее то, что ей нужно, чтобы гарантировать, что каждая
комбинация параметров, которая будет возвращена, корректна для данного
состояния базы данных. Это касается как очевидных вещей, например,
идентификаторов существующих сущностей, так и бизнес-правил. Например,
поскольку человек, не имеющий доступа к проекту, не может быть назначен
на задачу этого проекта, то наш метод,
Этот метод, ActionEnterInfo, может также принимать какие-то параметры, которые будут каким-то образом влиять на то множество, которое он задаст. Это используется в тех случаях, про которые я упоминал выше: углубленное тестирование целых чисел (положительных, отрицательных, нулевых и NULLевых) и дат. Scenario-метод Наконец, последнее, что нужно, чтобы заставить всё заработать – это собственно метод с описанием сценария тестирования. Этот метод простой: он возвращает бесконечную (или конечную, если хочет) нумерацию (перечисление) групп шагов. Каждая группа содержит один или несколько шагов, которые выполняются друг за другом. Для каждой группы имеется возможность уточнить, оборачивать ли эти шаги в транзакцию, как себя вести в случае ошибки, и т.д. Многопоточный Scenario-метод Многопоточная версия этого метода возращает массив енумераций (IEnumerable[]). 6. Фишки Кроме того, была написана еще и такая вот функциональность:
7. Пару слов о результате Что ж, статья подходит к концу и я благодарю всех, кто до этого момента дочитал А результат моей работы состоял в том, что я легко нашёл около пятидесяти багов, которые ни за что не нашёл бы без фреймворка, легко отлавливал дедлоки и следил за производительностью системы. В целом создание данного тестового фреймворка полностью себя оправдало и, что не менее важно, окупило. Благодарю Сашу Лавриновича: многие мысли здесь — его. Благодарю Вову Кривенко: эта статья появилась скорее благодаря ему, чем благодаря мне (я бы в жизни ее не написал, если бы он не напомнил мне раз 100). Благодарю наш проект: Birdview Projects. |
Новости >