Как генерация тестовых данных вернула доверие к тестам |
22.01.2025 00:00 |
Автор: Кирилл Корнаков Если вы когда-нибудь сталкивались с автотестами, которые ломаются на ровном месте, не дают предсказуемых результатов или отнимают больше времени, чем ручное тестирование, — эта история для вас. Наша команда столкнулась с похожей проблемой: тесты, которые должны были ускорять разработку, превращались в источник боли и хаоса. Мы больше не доверяли их результатам: красные прогоны стали «фоновым шумом», а зелёные — чем-то из области фантастики. В этой статье я расскажу, как мы разбирались с нестабильностью, рассмотрев три разных подхода (быструю починку тестов, создание идеальной базы данных и генерацию тестовых данных), и выбрали тот, который позволил нам ускорить CI/CD и вернуть контроль над автотестами. Контекст проекта Команда ERM в 2ГИС отвечает за систему, в которой хранится юридическая, производственная, финансовая информация о всех договоренностях при продаже услуг. Это точка входа для менеджера по продажам. Функционал практически полностью покрыт автотестами. Казалось бы, отличная история: тесты позволяют нам избегать ручного регресса и катить фичи по необходимости. Однако на практике оказалось, что тесты использовались далеко не так эффективно, как хотелось бы. Проблема: тесты, в которые никто не веритНа первый взгляд, процесс работы с автотестами выглядел понятно и логично↓
Основная боль заключалась в том, что тесты не давали полной гарантии, что всё хорошо. Вот почему автопрогону никто не доверял: 1. Красный прогон стал обыденностью. Автопрогон никогда не бывал зелёным. Даже на master-ветке тесты всегда завершались с ошибками. В итоге красный прогон стали восприниматься как норма, а не как сигнал о реальных проблемах. 2. Непредсказуемость прогона. Каждый запуск автотестов давал разные результаты: вчера падало 40 тестов, сегодня — 42. Даже без изменений в коде тесты могли сломаться или починиться сами. Невозможно было определить влияние нового изменения. 3. Долгое время выполнения. Прогон занимал больше часа, но даже после его завершения нужно было тратить время на ручную проверку. Это время не критично, если запускается ночной прогон, но если хочется сделать что-то современное, модное, то хотелось бы получать обратную связь быстро и без привлечения людей. Но эти проблемы не существовали изолированно. Они приводили к организационным трудностям, которые ещё сильнее подрывали доверие к автотестам и замедляли весь процесс. Вот как это проявлялось:
Откуда растут проблемыМы начали разбираться, почему всё пошло не так. Не хватало ресурсов QA. Нашей системе уже больше 10 лет. За это время над ней работало множество инженеров. Иногда были периоды нехватки ресурсов QA инженеров. Это привело к тому, что автотесты не создавались и/или их поддержка оставалась на минимальном уровне. Тестовые данные брались из БД. Для наших автотестов использовались реальные данные из базы, что изначально казалось удобным решением. Но на деле это стало причиной множества проблем, так как такой подход не учитывал, что:
Системная зависимость. Система включала множество связанных сущностей, где один объект зависел от другого: заказы, договоры, юридические лица и множество других таблиц. Малейшее изменение данных могло вызвать цепную реакцию падений. И вот так у нас появилась целиМы хотели выйти из этой печальной ситуации. И поставили перед собой следующие цели.
Пути решенияМы рассмотрели три подхода:
Теперь про каждый подход подробнее. Подход 1: Быстрая починка тестовИдея заключалась в том, чтобы быстро исправить 50 падающих тестов. На первый взгляд, казалось, что задача по силам: разобрать 12 тестов на каждого из четырёх тестировщиков. Однако реальность оказалась сложнее. Я попытался схитрить и временно убрал их из прогона, чтобы получить «зеленый» результат. Однако тесты продолжали падать — сначала ещё 50, потом ещё, пока их не накопилось около 200. Стало ясно, что такой подход не работает: никто больше не позволит просто исключать тесты. Дополнительно осложняло ситуацию то, что при большом количестве условий тесты начали падать по тайм-ауту (выборки из базы занимали больше 30 секунд). Увеличение времени выполнения тестов противоречило нашим целям. Мы поняли, что даже если решим одну проблему, столкнемся с другой. Нужно было менять подход. Например, уменьшить объем данных в базе, чтобы тесты работали быстрее. Подход 2: Идеальная база данныхИдеальная база данных – это, конечно, красивая идея:
Но на деле мы столкнулись со следующими сложностями:
В итоге, даже с «идеальной» базой, вопросов возникло больше, чем ответов. Так как нам всё равно пришлось бы создавать новые объекты в скриптах, то почему бы не начать это делать в более привычной для нас среде — автотестах. Подход 3: Генерация тестовых данныхВместо выбора объекта из БД мы начали создавать данные заново для каждого теста. Преимущества генерации данных:
Минус — объекты создаются не молниеносно, особенно если нужно построить цепочку зависимостей для полной изолированности. Иногда это занимает секунду или больше. Раньше подготовка данных выглядела следующим образом:
Это тест на смену юридического лица. Мы выбираем нужный заказ из запроса, убираем лишние краевые значения, которые не нужны для теста. В процессе подключаемся к нескольким таблицам. Затем выбираем юридическое лицо с нужными нам условиями, а после — его профиль. Для одного теста получилось три запроса, которые хранятся внутри теста. Теперь вместо сложных запросов мы перешли к генерации:
Я заменил 20 строк на 3 строки, в которых создаётся каждый независимый объект. Из-за этого добавились ещё пару плюсов. 1. Улучшилась читаемость: вся лишняя логика по выборке данных ушла из теста. 2. Тесты стали проще, что значительно облегчило их поддержку, отладку и дальнейшую работу с ними. Для нового человека процесс станет гораздо удобнее и понятнее. Как я упоминал ранее, наши объекты зависит друг от друга, поэтому внутри генератора могут быть дополнительные генераторы данных для других сущностей. Далее заполняется модель, например, модель заказа: данные, которые не нужно менять, задаются по умолчанию, а остальные заполняются сгенерированными в этом методе значениями.
В дальнейшем мы отправляем всё на создание, которое бывает двух типов: через API и DB. Если это наш объект, т.е. мы являемся для него мастер-системой, то у нас есть возможность создавать и обновлять его через API. Это легальный и правильный способ, который всегда корректно сохраняет данные в таблицах. Альтернативный способ — создание через базу данных. Этот способ подходит, например, для объектов, которые приходят по импорту из других систем. Обычно такие объекты простые, занимают 1–2 таблицы и содержат минимум информации, так как основная информация хранится в мастер-системах. В этом случае мы просто делаем Insert в базу для нескольких объектов. Чаще всего достаточно стандартной операции «create order», которая покрывает 80% случаев, но если нужно создать особенный объект, то мы явно меняем необходимые поля.
Это создание заказа с разными параметрами (например тип, период размещения и т. п.). Если нужно изменить параметры, это легко делается через лямбда-функцию. Такой подход можно реализовать на любом языке программирования (здесь C#). По сути, мы реализовали паттерн билдер. РезультатыНаша работа с автотестами доказала, что стабильность и предсказуемость тестирования начинаются с правильной работы с данными. После перехода на генерацию тестовых данных мы добились:
Конечно, наш подход — это не универсальное решение, а лишь один из способов решения проблемы. Если используется исторические данные для автопрогонов, и тесты успешно проходят, то переход на генерацию может только ускорить прохождение прогона, если ваши выборки занимают много времени. А если ваши данные простые и редко меняются, то можно обойтись скриптами и статичной базой. Но в случае необходимости постоянно актуальных данных, подход с генерацией данных оказался не только самым эффективным, но и самым масштабируемым — теперь мы уверены в том, что наши тесты отражают реальное состояние системы, а не случайные сбои. Надеюсь, наш опыт может пригодиться и вам:) |