Я на рельсах сижу не так давно, но, тем не менее, уже успел кое в чем покопаться. Одна из тем, с которыми пришлось разобраться довольно тщательно — это fixtures и их альтернативы в тестах rails.
Коммьюнити пошло дальше и придумало несколько решений (1, 2) упрощающих создание объектов с данными в обход фикстур.
Поэтому проще на самом деле решить указанные в начале статьи недостатки в рамках самих фикстур, чем переключаться на решение, которое создает новые проблемы. Вот как:
Был бы благодарен за комментарии, дополнения и мнения профессионалов.
Немного о самих Fixtures
Маленький обзор для тех, кто не совсем в теме. Fixtures в Rails — это штука, которая позволяет загонять в тестовую БД заранее подготовленные данные, которые используются тестируемыми объектами. В самом коде теста достаточно вызвать методfixtures :users
и он автоматически загрузит все данные для объектов класса User. Для более подробного ознакомления, следует прочесть мануал (он, кстати, короткий).Почему разработчики не любят Fixtures?
Несмотря на то, что этот инструмент призван был помочь разработчикам, решая одну проблему, он создал несколько других. Вот основные недовольства фикстурами, которые обычно высказываются в блогах:- Фикстуры по замыслу специфичны для каждого теста, но, тем не менее доступны каждому из тестов. То есть, если в одном тесте мне нужны одни данные для объектов User, а в другом уже несколько другие — со стандартными фикстурами это сделать будет невозможно.
- Если записей для БД оказывается не очень мало, фикстурами становится гораздо сложнее моделировать такие отношения объектов, как has_one, has_many, habtm и has_many :through — нужно уследить за правильными значениями foreign keys. Хотя, на самом деле, стандартный механизм предоставляет удобный способ избежать подобного геморроя (см. ниже), об этом часто забывают.
- Ненаглядность. Некоторые разработчики хотят четко прямо в коде теста видеть какие данные буду использоваться.
Какие есть альтернативы?
В качестве альтернативы фикстурам можно прежде всего создавать объекты прямо в тестах, например так:%w("Tom Bill Frank").each { |name| User.create(:first_name => name) }
. Я пробовал так делать, но недостаток тут в том, что после каждого теста вам придется как-то вручную очищать тестовую базу. Если при этом такие тесты у вас будут запускаться вместе с тестами, все еще использующими фикстуры, то могут возникнуть конфликты — фикстуры используют транзакции для заполнения/очищения БД, поэтому перед выполнением нефикстурного теста может оказаться, что база, на самом деле, не очищена. (Например у меня из-за этого нефикстурный тест, запущенный в одиночку, успешно проходил, но запущенный вместе с другими тестами — выдавал ошибку).Коммьюнити пошло дальше и придумало несколько решений (1, 2) упрощающих создание объектов с данными в обход фикстур.
Почему, все же, может быть удобнее использовать Fixtures?
Представьте, что вы добавляете в свою модель новое поле и валидацию его присутствия. Теперь представьте, что у вас 10 нефикстурных тестов, в которых создаются и заполняются данными объекты. Не слишком большое удовольствие вас ждет, когда вы будете все эти 10 тестов править (проблема, кстати, вполне жизненная — именно так произошло, когда я начал встраивать в модель плагин restful_authentication и добавил поле password). Юнит-тесты на то и юнит-тесты, что не должны больше ни от чего зависеть и должны использовать только те данные, которые подразумеваются для тестирования функционала модели.Поэтому проще на самом деле решить указанные в начале статьи недостатки в рамках самих фикстур, чем переключаться на решение, которое создает новые проблемы. Вот как:
- Проблема специфичности фикстур для каждого теста решается плагином FixtureScenarios. Теперь в каждом тесте достаточно указать
scenario :logging
и из директории fixtures/logging загрузятся все фикстуры. По ссылке выше, в статье упоминается дополнительный плагин FixtureScenarioBuilder — я пытался его использовать, но во-первых, на тот момент там явно был баг, из-за которого все тесты падали, и во-вторых — особого смысла в нем и не было, т.к. динамическое создание кучки фикстур в три строки можно организовать прямо в YAML-файлах при помощи ERb (см. раздел мана по fixtures «Dynamic fixtures with ERb»). - Проблема как уследить за кучей записей с foreign keys решается стандартным способом, предусмотренным фикстурами:
### in pirates.yml
reginald:
name: Reginald the Pirate
monkey: george
### in monkeys.yml
george:
name: George the Monkey
pirate: reginald
В данном случае id записи не задается явно (кстати, не вижу смысла это вообще делать когда-либо в фикстурах), а присваивается автоматически. Рельсы также автоматически подставляют вместо george и reginald соответствующие id. И вот уже мы имеем отношения has_one и belongs_to. Более подробно об использовании этого подхода опять таки написано в мануале, в разделе «Advanced YAML Fixtures». Особо мощно данный подход можно использовать комбинируя его с генерацией записей при помощи ERb.
- Проблема наглядности, на мой взгляд, также решается использованием плагина FixtureScenarios. Именованные сценарии позволяют вам один раз продумать какие данные будут загружены для этого сценария и не возвращаться к этом вопросу в будущем, просто контролируя какие именно сценарии используются в тесте. При необходимости можно добавить новые сценарии, которые, кстати, могут быть вложенными (тогда, например, сначала загрузятся данные из fixtures/logging, а потом из fixtures/logging/admin).
Был бы благодарен за комментарии, дополнения и мнения профессионалов.