Pull to refresh

Comments 19

Как я понял, вся статья сводится к принципу "используй в тестах то же окружение, что и на проде".
Кстати, опечатку нашел:
> SQLite также содержит несколько других вещей, которые SQLite не поддерживает

В целом - да, именно это и вынесено в TLDR в самом начале статьи. Но статья раскрывает в деталях - с какими проблемами можно столкнуться - если не следовать этому правилу.

По поводу замечания - спасибо! Переформулировал предложение в переводе

Упс, и точно: все прочитал, кроме этого предложения. =)

Самое забавное, что статья рассказывает о том что PHP не умеет обеспечивать типобезопасность из коробки, поэтому SQLite виноват.

от фреймворка зависит. в laravel поля из БД не мапятся на поля модели (хранятся в одном массиве).
в симфони вроде бы не так.
ну и это только по п1. виновато то что в каждой БД есть свои особенности.

Про одинаковое окружение говорят в тех же 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 тесты

Вопрос в том, ЧТО именно проверяют модульные тесты. Часто тесты проверяют логику работы приложения, и для это логики без разницы какая БД используется, так как ORM убирает базовые операции под капот.

Если же используются специфичные вещи, как например регистронезавимый LIKE или json_* операции, то такие лучше проверять в целевой СУБД. Но обычно, такие тесты размещаются в подсистеме работы с БД у которой свои тесты которые могут подключаться к целевой БД.

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

У нас в системе используются комбинированные тесты: там где есть зависимость от движка - есть модульные тесты движка, где нету - используется Sqlite в памяти.

Sign up to leave a comment.

Articles