Comments 19
Как я понял, вся статья сводится к принципу "используй в тестах то же окружение, что и на проде".
Кстати, опечатку нашел:
> SQLite также содержит несколько других вещей, которые SQLite не поддерживает
В целом - да, именно это и вынесено в TLDR в самом начале статьи. Но статья раскрывает в деталях - с какими проблемами можно столкнуться - если не следовать этому правилу.
По поводу замечания - спасибо! Переформулировал предложение в переводе
Самое забавное, что статья рассказывает о том что PHP не умеет обеспечивать типобезопасность из коробки, поэтому SQLite виноват.
Про одинаковое окружение говорят в тех же 12factor, но, понимать это требование буквально, такое себе занятие ИМХО. Есть важные дядьки, такие как Бек, Мартин, Фаулер. Они в унисон твердят о том, что тетсирование это основа разработки ПО и тесты должны запускаться максимально быстро и просто и проходить должны тоже максимально быстро. Для этого мы(разработчики) выстраиваем пирамиды тестирования, мокаем не важные для конкретного теста зависимости и т.д.
Если для запусков тестов у вас должны быть запущены контейнеры с MySQL/PostgreSQL, Redis и проими, то это не то, о чем говорят вышеперечисленные дядечьки. На Apple M1, например, докер работает в 3-5 раз медленнее и те же 1000 тестов будут гнаться пол минуты минимум.. а с учетом того, что в обычном режиме тесты запускаются минимум раз в минуту (привет TDD), то скорость разработки будет оставлять желать лучшего.
Я для себя нашел одно решение и пока оно меня не подводит. При локальной разработке гоняю тесты на SQLite. Но в CI/CD при каждом пуше в remote, прогоняются тесты на той БД, которая в проде. Таким образом тесты гоняются быстро при разработке и требование 12factor удовлетворяется.
Это решение справедливо, когда вы не используете чистый SQL, а используете какую либо ORM которая прячет особенности каждой БД за одним интерфейсом.
В целом согласен, довольно взвешенная точка зрения.
Однако есть определенные неудобства с миграциями, которые я указал в послесловии (фулл текст индексы, изменение внешних ключей, удаление нескольких колонок). Они решаемы определенными костылями наподобии:
if (DB::getDriverName() != 'sqlite') {
...
}
И потом незаметно весь проект превращается в эти леса костылей :D
Стоит отметить, что ни бизнес логика, ни слой приложения, ни инфраструктурный слой и ни какой другой слой вашего приложения, не должны знать о том, в каком окружении они запущены. Это все конфигурируется конфигами перед запуском (переменные окружения, механизм конфигов и т.д.). Об этом также явно говорят все те же 12factor. Поэтому ваш костыль делает архитектуру менее гибкой, плодит "особые" знания для новых разработчиков проекта или, если сказать проще, удорожает поддержку и развитие кодовой базы. На старте проекта это не критично, но "теорию разбитых стекл" никто не отменял и вскоре кодовая база может обрасти такими костылями по самое немогу. В вашем случае, нужно не просто делать одинаково, а вынести эти костыли в одно место и попытаться соблюсти наш любимый DRY.
Что касается миграций, то это механизмы другого порядка и никакого отношения к приложению не имеют. Приложение ожидает уже готовую к использованию БД со всеми индексами, таблицами и т.д. Это справедливо и для unit-тестов. Механизмы миграции лучше разрабатывать и тестировать отдельно от приложения, если вы не хотите использовать сторонние, готовые, протестированные решения. Миграции накатываются перед запуском приложения. Отмечу, что код таких механизмов может храниться и рядом с приложением, но не быть его частью. Т.е. миграции не должны влиять на работу приложения напрямую.
Я попробовал Memory Engine мускула - не поддерживает BLOB/TEXT.
Остановился на докер контейнере, создающем базу в ramdisk (tmpfs).
В тестах можно указывать теги группировки.
Тогда можно одной командой прогонять тесты для группы sqlite, а совсем другой запускать тесты для production-like базы.
Профит в том, что базовая функциональность тестируется молниеносно быстро, а уже на стороне CI/CD прогон идет по всем тестам. Как говорится, для gitlab-runner времени не жалко.
Ну и первый пример высосан из пальца: там пользовательский ввод без валидации, да и если придумать, что математика может выдавать float, а нужен int, то перед сохранением должна быть валидация на результат математики. Имхо.
Перестаньте использовать библиотеки, не умеющие абстрагировать от вас детали БД. Да и в целом, на PHP писать перестаньте.
Ну и, на секундочку, SQLite - одна из самых высокопроизводительных БД.
Перестаньте использовать библиотеки, не умеющие абстрагировать от вас детали БД.
en.wikipedia.org/wiki/Leaky_abstraction
Да и в целом, на PHP писать перестаньте.
— Филин, ты такой мудрый! Скажи, как нам, мышам, выжить в этих ужасных лесных условиях, когда мы такие маленькие и все за нами постоянно охотятся?
Филин подумал и ответил, глядя куда-то вдаль:
— Вам, мыши, надо стать ёжиками!
— Гениально! — воскликнула Мышь и убежала.
Проходит несколько минут, Мышь снова возвращается к Филину:
— Слушай, Филин. А как нам стать ёжиками?
— Иди-ка ты отсюда, Мышь, — с той же задумчивостью произнёс Филин. — Я стратег, а не тактик!
Ну и, на секундочку, SQLite — одна из самых высокопроизводительных БД.
1) СУБД
2) «Одна из» это из скольки?
3) высокопроизводительная это дает больше RPS чем другие СУБД на равном железе или тут какое-то другое понимание производительности?
Но ведь если вы используете внешнюю бд, то это уже не Unit тесты
А что вы думаете насчет MySQL MEMORY Storage Engine?
Там правда не поддерживаются TEXT/BLOB...
Вопрос в том, ЧТО именно проверяют модульные тесты. Часто тесты проверяют логику работы приложения, и для это логики без разницы какая БД используется, так как ORM убирает базовые операции под капот.
Если же используются специфичные вещи, как например регистронезавимый LIKE или json_* операции, то такие лучше проверять в целевой СУБД. Но обычно, такие тесты размещаются в подсистеме работы с БД у которой свои тесты которые могут подключаться к целевой БД.
С другой стороны, в модульных тестах можно использовать одну конфигурацию тестовой БД, а вот приемочные тесты гонять на всех конфигурациях СУБД. То что мы ошибку не увидим быстро это минус, но переусложнять тестовую среду тоже не вариант.
У нас в системе используются комбинированные тесты: там где есть зависимость от движка - есть модульные тесты движка, где нету - используется Sqlite в памяти.
Перестаньте использовать SQLite в Unit-тестах