#yii2 #activerecord #юнит_тесты #codeception
Дано: Проект с большим количеством Active Record и большим количеством Foreign Keys
между ними. Сейчас для тестирования в целях независимости тестов друг от друга каждый
тест загружает в базу данных фикстуры.
В случае если тесту требуется пользователь он загрузит в базу данных фикстуру UserFixture
и все зависимые фикстуры.
Проблемы:
Количество зависимостей очень большое, в базу данных грузится не только фикстура
пользователя, но ещё и ещё больше 10 зависимых фикстур.
Такую загрузку в базу требует каждый тест.
Обычно тестам нужен не пользователь, но даже в этом случае для корректной подготовки
БД его необходимо загрузить, так как фикстура пользователя скорее всего будет являеться
зависимостью.
Сейчас у меня почти все юнит тесты содержат фикстуры и обращаются к базе данных.
Количество юнит-тестов стало большим и это начало доставлять проблемы.
Как лучше всего использовать фикстуры для тестов?
Я вижу два варианта:
Загрузить фикстуры в базу данных один раз и в тестах вручную возвращать измененные
значения назад к исходным. (Крайне тяжело, вариант сразу отпадает).
В тестах готовить необходимые объекты Active Record, а базу данных использовать только
для тестов которые вызывают insert или update в базу данных. (Приоритетный вариант,
но не слишком накладно будет перед каждым тестом готовить необходимые модели?)
Как правильно писать тесты в контексте веб-разработки, где большая часть операций
идет с использованием Active Record? Как добиться ускорения работы тестов?
Приведите пример как в вашем проекте организованы тестирование методов, которые используют
Active Record.
Ответы
Ответ 1
Это довольно классическая проблема тестирования, и ее решение заключается в выкидывании зависимостей. Вы не должны тестировать базу данных, поэтому никакие фикстуры вам на самом деле не нужны вплоть до того момента, как вы подойдете к системному/интеграционному тестированию сервиса целиком. На уровне модульного тестирования вы тестируете одну конкретную feature с очень узкой областью видимости, где все внешние зависимости можно (и нужно) подменить на моки, которые не только освоодят вас от проблем с БД, но и ускорят тесты. У вас скорее всего уже появился вопрос - что делать с теми местами, в которых напихано штук десять моделей, которые приходят через статические методы. По-хорошему этих мест вообще не должно быть: приложение должно делиться на слои, и слой базы данных должен без проблем подменяться в вышестоящих менеджерах бизнес-логики (хотя саму бизнес-логику в модульных тестах я бы не стал тестировать, отдав это все на откуп более высокоуровневым тестам). Впрочем, с базой данных есть еще одно решение, которое я не устану пиарить - это Docker. Docker позволит вам сохранить базу данных в определенном состоянии и возвращать ее к нему же путем переподнятия контейнера; пул из десятка перезапускаемых контейнеров может предоставлять новую БД практически мгновенно. Впрочем, примерно то же самое можно сделать на основе SQLite.Ответ 2
Ответ состоит в том, что вы должны использовать моки вместо реального обращения к базе данных в каждом тесте. По сути это второй вариант решения, который вы предложили. На вашем примере: пусть есть метод saveCalculatedPrice, который поручает одному классу посчитать сумму, а другому классу - сохранить ее в бд. Этот метод надо тестировать на моках. Расчет суммы выполняет один класс и это поведение протестировано в тестах этого класса. Сохранение выполняет другой класс и это поведение протестировано в его тестах. Задача функции saveCalculatedPrice поочередено обратиться к этим двум классам, вот эту задачу и надо тестировать. Сделать это можно только с помощью моков, иначе вы неизбежно будете повторно тестировать поведение уже протестированное в других местах и иметь дело с теми проблемами которые вы описываете в вопросе. По поводу накладности написания моков для тестов, вопрос в том как вы делаете моки. Если вы пишете все моки сами, то это действительно очень трудоемко, чтобы требовалось меньше рутинной работы посмотрите в сторону mock-фреймворков, например phake. Вообще, как советуют в соседнем ответе, я бы не тестировал работу с бд в юнит-тестах и оставил бы это на откуп интеграционным тестам. Вместо этого в юнит тестах везде где надо обратиться к реальной базе данных я бы подставлял мок. Когда вы дойдете до написания интеграцинных тестов, вы будете тестировать ваше приложение на более высоком уровне, и вам скорее всего не потребуется готовить бд отдельно для каждого теста. Вместо этого лучше организовать тестирование следующим образом: создать тестовую бд поочередно выполнить на ней все интеграционные тесты То есть интеграционные тесты будут выполняться в среде приближенной к реальной среде, в которой выполняется приложение.
Комментариев нет:
Отправить комментарий