ALEXPTS
WEB DEVELOPER
PHP, NodeJS, JavaScript, etc.
Главная Приколы Стена
Cкилы: Web Php NodeJs Tests Storages

Погружение в codeception

#Тесты

Последние пол года работаю над финским проектом по планированию встреч/мероприятий в реале. Наша команда занимается автоматизацией одной из частей бизнеса.

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

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

Происходило это по ряду причин: 1) Фиче-ветку нужно влить минимум в 3 ветки, это обусловлено историческим процессом версионирования на проекте. Процесс построен таким образом, что перед слиянием веток, нужно чтобы CI бот прогнал все автоматические тесты и дал добро на слияние.

Время выполнения набора автоматизированных тестов (unit, функциональные, интеграционные) составляет порядка 15-17 минут на весь прогон. Процесс слияния фичи занимает таким образом от 45 минут по чисто технической причине, что мне как разработчику не нравится.

2) Бывает так, что тесты не проходят с первого раза. Тесты проходят через несколько перезапусков, без изменения кода или тестов.

3) Интеграционные тесты сильно зависят от порядка выполнения, каждый тест может изменить общее состояние базы данных и следующий тест может не пройти. Такое случалось несколько раз, что изменения в одном тесте, приводили к тому, что система вела себя иначе в последующих тестах и в итоге CI показывал красный цвет. Усугубляется это все тем, что над проектом работает несколько перекрестных команд из разных стран и все постоянно что-то меняют. Уследить за всем что меняет команда примерно из 20 человек практически нереально.

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

После некоторых исследований и размышлений, удалось для порядка 90% интеграционных тестов заменить фазу авторизации. Раньше на каждый тест делался 1 или 2 запроса аутентификации по HTTP (зависит от подсистемы проекта, к которой шел запрос). Для тестов, которым не требовались определенные права или конкретные учетные записи, была подменена аутентификация без HTTP запроса. Демо пользователь и его токен доступа были вынесены в фикстуры, которые загружались 1 раз перед прогоном интеграционных тестов.

Это позволило сократить число HTTP обращений к серверу и ускорить примерно на 7-8 минут от времени общего прогона. Также решить проблему с уникальностью токена на стороне сервера. Уникальность токена основывалась на 6 значной временной метке от текущей секунды. Были нечастые случаи, когда значение милисекунд совпадало и появлялся дубликат токена, который валил процесс при сохранении в БД.

Далее была пересмотрена работа интеграционных тестов. Заменили в codeception модуле REST зависимость на Yii2 c PhpBrowser. Что позволило нам выкинуть nginx и php-fpm из процесса тестирования. Модуль эмулировал работу http запроса в рамках своего процесса и вызывал приложение с нужными входными параметрами.

Это дало еще небольшой прирост к времени, порядка 30-60 сек. А также стало основой для дальнейших улучшений, за счет того, что все стало работать в 1 потоке. Тест стал запускать приложение в этом же процессе.

Далее дошли руки до борьбы с качеством тестов и зависимости тестов друг от друга. Решили сделать честные фикстуры для уже функциональных тестов (бывшие интеграционные). Чтобы на каждый тест состояние базы было предсказуемо одинаковым. На codeception хук _before сделали загрузку фикстурных данных. На codeception хук _after сделали очищение БД.

Время выполнения тестов выросло в разы :)

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

Разобраться в действующих фикстурах и бизнес логике местами очень сложно. Добавление новых данных в фикстуры может легко сломать другие тесты и разобраться в связях между фикстурами, которые в codeception описываются как простые php файлы с массивами бывает не просто.

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

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

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

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

Осталось решить проблему со скоростью загрузки фикстур и чистки базы. За счет того, что тесты стали запускать приложение в своем процессе, а не бежать по HTTP к nginx и порождать новый процесс, стало доступно очень просто откатывать состояние базы данных через транзакцию.

Написал модуль для codeception, который перед началом теста стартует транзакцию, а в конце теста откатывает транзакцию, дешево возвращая состояние БД в исходное состояние.

По мере переноса тестов всплыла проблема, модули codeception Yii2 и DB работают с разными подключениями к базе данных, каждый модуль описывает свое подключение и работает через него. Уровень изоляции чтения в рамках открытой транзакции не отображает изменения в БД для других соединений, только в рамках этого же соединения, что вполне логично.

Так как большая часть модулей умеет работать с Yii2 модулем в качестве зависимости, то все переделал на Yii2 модуль и избавился от DB модуля. Дописав свой маленький модуль, который по семантике повторяет недостающую нам функциональность из DB модуля.

Рефакторинг подходит к концу. Он позволил сократить время выполнения тестов, уменьшить число зависимостей для тестирования. Сделать честные фикстуры и сброс состояния на уровне теста для функциональных тестов. Локализировать и инкапсулировать почти все зависимости теста в самом тесте. Тест читается однозначно и все в одном месте. Подготовить все необходимое к следующему рефакторингу, в рамках которого планирую параллелить тесты и улучшать итоговое время прогона набора тестов на CI.

Лично я получил кайф от улучшения процесса и упрощения жизни всем командам, а также более глубоко погрузился и изучил тестовый фреймворк codeception. А еще получил медальку за проделанную работу от коллег :)

P.S. По итогам рефакторинга сделал несколько PR в используемые иснтрументы. PR были приняты и изменения появятся в следующих версиях продутктов.