Комментарии 5
Если сходить в документацию, то можно найти ещё несколько правильных вариантов. А это всего-то один абзац из доки https://laravel.com/docs/8.x/database-testing#defining-relationships-within-factories
А как быть при таком подходе в системах разделенных на модули? Когда есть отдельно модуль "пользователи" и отдельно модуль "новости", а тестирование по кейсу подразумевает первоначальное заполнение базы сидами. Особенно если у пользователей заранее есть роли, которые следует использовать, которые хранятся в БД и на роли так же созданы фабрики используемые в сидах?
Не до конца понятно, что вы подразумеваете под "модулями". Но подход в создании фабрики остается таким же. Каждая фабрика должна быть независима. Фабрика новости будет использовать фабрику пользователя. Фабрика пользователя будет использовать фабрику ролей. Если без этих связей модель нельзя создать.
Но в сидерах вы реализуете свою логику заполнения полей. Создаете роли, затем к этим ролям пользователей. И новости привязываете к существующим пользователям. Если вам не нужна работа фабрик внутри фабрик.
В нашем примере тестируется работа страницы списка новостей. Если мы правильно поняли, то в вашем тесте вам нужно протестировать работу новостей при определенных ролях пользователя. У вас часть касающаяся подготовки данных для теста не будет такой же простой. Вам в явном виде внутри теста нужно использовать нужную роль и пользователя с этой ролью.
Я бы даже рекомендовал сделать дополнительные классы/методы для генерации данных в тестах. Которые бы создавали пользователя с нужной ролью (и авторизовывались под ним при необходимости).
Что-то в таком духе:
$admin = $this->actingAsAdmin();
Создаем роль (если ее нет) к ней создаем пользователя и затем уже остальная логика теста. Вы также можете вызывать конкретные сидеры непосредственно из тестов:
https://laravel.com/docs/8.x/database-testing#running-seeders
Думала пару дней, стоит ли ввязываться в дискуссию, поскольку при нынешней моей карме я могу только в один комментарий в сутки. Но вдруг тут больше понадобится? Штош, договоримся, что завтрашний мой комментарий я мало ли ещё где оставлю, если вообще вообще попаду на хабр. Если из этого обсуждения я исчезну, короче, это ни разу не безразличие к теме.
Во-первых, мне кажется, что тестирование через .env - это зло. Есть такая же sqlite inmemory, надо только расскомментировать две нужных строчки в phpunit.xml . В новых версиях ларавель это - готовые строчки, именно про sqlite & inmemory.
Чем это лучше захода через .env? Ну, например, это вас заставляет думать о минимальных средствах. В базе инмемори вы не храните таблицы, скажем, всех номеров всех рейсовых автобусов по стране, чтобы провести тест страницы, где юзер покупает билеты - что юзер может, скажем, выбрать номер автобуса. Вы всегда будете иметь в виду, что десятка-другого автобусных рейсов достаточно.
Да, порой туда хочется заглянуть физически, через привычный какой-нибудь phpMyAdmin, тогда нужен .env. Но всё таки это, по моему опыту, дурной тон - особенно если это настраивается начальством. Часто сопровождается левой архитектурой. Вынужденной работой на сервере вместо домашней машинки. Так (на домашней) я могу запускать тесты раз в пять минут, на удалённой - раз в час. Кроме того, на сервере кто-то ещё тестирует, вклинивается между моими тестами. База растёт как на дрожжах - её временами чистят, из этого возникают ошибки, где вообще не ждали... Бррр.
Далее.
Что означает эта волшебная единица? Я тоже не знаю. Так код писать нельзя, поэтому отбрасываем этот вариант
Стоп. Это код теста. Сидеры юзают-то обычно в тестах. В тестах обычно именно ТРЕБУЕТСЯ подконтрольность. Именно что волшебная единица может быть очень кстати. Хотя дальше возможно развитие от единицы шире. Но для начала это - самое как раз то.
Чисто имхо - я не готова спорить.
Спойлер: фабрики не должны зависеть от сидов.
Давайте-ка разберёмся, кто вообще такие "фабрики" с "сидерами". Чтобы сама постановка вопроса из этого спойлера ("А зависят ли фабрики от сидеров? А да или нет?") стала бы чисто на слух радикально абсурдной.
"Фабрика" в ларавель - это местное применение паттерна "фабрика", как я понимаю. В паттернах проектирования "фабрика" - это класс, который штампует объекты сходной природы, но с небольшими возможными контролируемыми различиями. В данном случае (на ларавель) нам для тестирования нужны типовые записи в очередную таблицу БД. Скажем, у юзера должны быть имя и мейл, у разных юзеров разные. У статьи должны быть автор, заголовок и текст, и тоже желательно разные для разных статей. Так вот, фабрика юзеров штампует данные для очередной записи "юзер" в таблице "users". Фабрика статей штампует данные для очередной записи "статья" в таблице "articles". В самом банальном виде "штампует" - как ассоциативный массив.
Ну то есть да, это - массивы с доп.навыками. Они, например, умеют себя сами вписывать в базу. Но это уже детали.
Фабрика может в фейкер. Фейкер - от слова "фейк" - это отдельный проект, есть на гитхабе. Он-то нам и обеспечивает контролируемые различия в выдаче. Те самые, ради которых мы обращаемся к фабрикам. У фейкера есть свои слабости - скажем, работа с кириллицей. Я уже точно не помню, в чём было дело, но помню, что как-то какую-то кириллическую задачу пыталась с ним разрешить, в итоге сделала, но неприятно хитро. Под ту же задачу с латиницей у него была функция, готова функция.
Теперь о сидерах. Сидер - это сеятель. От слова seed - семечка. Сидер засеивает БД нашими записями. И если записи требуются ну совсем стандартные (скажем, пять записей вида "статья с названием СтатьяЭнная - от "СтатьяПервая" до "СтатьяПятая", с текстом Лорем100 - просто с одним и тем же, автором ЮзерЕдинственныйТоЕстьСАйди1", проще всего такое вот создавать на месте, в сидере прямо, без всяких фабрик). Просто тупо через Article::create([тут отдаём ассоциативный массив для очередной записи]).
Если же нам нужна вариативность хитрее, сидер просто... ну, просто дёргает фабрику. Фабрика ровно в одном смысле зависит от сидера - если он не справляется сам, ей придётся включиться.
Только теперь все заработало корректно
Строчка 'author_id' => User::factory() с create далее или без - она не то, чтобы неправильная... она просто лишняя. Закомментируйте её и убедитесь, что всё работает так же. Потому что фабрика, повторим, это только станок для штамповки базовой формы записи в таблицу статей. И если часть данных вы передаёте отдельно как ассоциативный массив, то внутри фабрики эти же данные можно вообще не трогать.
Именно ассоциативный массив с айдишником автора вы передали в хвостике
"->create(['author_id' => $user])" в классе DatabaseSeeder.
Так что в вашем "правильном" способе написания фабрики вы строчку с вызовом User::factory() внутри класса ArticleFactory сделали безобидной, но ценности это ей не прибавило - она как была лишняя, так и осталась. Конечно, лишняя вредоносная строчка хуже лишней и безобидной, но всё таки:)
В целом это что-то вроде array_merge. Что-то приходит от фабрики, а что-то - внутри create(). Ну и потом перед вписыванием в БД оно мержится. А неприятности появляются именно при вписывании в БД - там изнутри не пускают без идентификатора автора. Если к моменту вписывания в БД идентификатор есть - базе уже безразлично, откуда он взялся. Изнутри фабрики или уже из массива в скобках.
Laravel: создание фабрик и seeders при связях между моделями