класс ActiveRecord::FixtureSet
Фикстуры — это способ организации данных, с которыми вы хотите проводить тестирование; короче говоря, образцовые данные.
Они хранятся в файлах YAML, по одному файлу на модель, которые размещаются в каталоге, указанном в ActiveSupport::TestCase.fixture_path=(path) (это автоматически настраивается для Rails, поэтому вы можете просто поместить свои файлы в <your-rails-app>/test/fixtures/). Файл фикстуры заканчивается расширением .yml , например: <your-rails-app>/test/fixtures/web_sites.yml.
Формат файла фикстуры выглядит следующим образом:
rubyonrails: id: 1 name: Ruby on Rails url: http://www.rubyonrails.org google: id: 2 name: Google url: http://www.google.com
Этот файл фикстур содержит две фикстуры. Каждая фикстура YAML (т.е. запись) получает имя и за ней следует отстуженный список пар «ключ: значение» в формате «ключ: значение». Записи разделяются пустой строкой для вашего удобства.
Примечание: Фикстуры не упорядочены. Если вам нужны упорядоченные фикстуры, используйте тип YAML omap. См. yaml.org/type/omap.html для спецификации. Вам понадобятся упорядоченные фикстуры, когда у вас есть ограничения на внешние ключи для ключей в одной таблице. Это обычно необходимо для древовидных структур. Пример:
--- !omap
- parent:
id: 1
parent_id: NULL
title: Parent
- child:
id: 2
parent_id: 1
title: Child Использование фикстур в тестовых случаях
Поскольку фикстуры — это инструмент тестирования, мы используем их в наших модульных и функциональных тестах. Существует два способа использования фикстур, но сначала давайте посмотрим на пример модульного теста:
require "test_helper"
class WebSiteTest < ActiveSupport::TestCase
test "web_site_count" do
assert_equal 2, WebSite.count
end
end
По умолчанию test_helper.rb загрузит все ваши фикстуры в вашу тестовую базу данных, поэтому этот тест пройдёт.
Тестовая среда автоматически загрузит все фикстуры в базу данных перед каждым тестом. Для обеспечения согласованных данных среда удаляет фикстуры перед запуском загрузки.
В дополнение к доступности в базе данных данные фикстуры также можно получить, используя специальный динамический метод, который имеет то же имя, что и модель.
Передача имени фикстуры этому динамическому методу возвращает фикстуру, соответствующую этому имени:
test "find one" do assert_equal "Ruby on Rails", web_sites(:rubyonrails).name end
Передача нескольких имён фикстур возвращает все фикстуры, соответствующие этим именам:
test "find all by name" do assert_equal 2, web_sites(:rubyonrails, :google).length end
Передача без аргументов возвращает все фикстуры:
test "find all" do assert_equal 2, web_sites.length end
Передача любого имени фикстуры, которой не существует, вызовет StandardError:
test "find by name that does not exist" do
assert_raise(StandardError) { web_sites(:reddit) }
end
В качестве альтернативы вы можете включить автоматическую инициализацию данных фикстур. Например, рассмотрите следующие тесты:
test "find_alt_method_1" do assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name'] end test "find_alt_method_2" do assert_equal "Ruby on Rails", @rubyonrails.name end
Для использования этих методов для доступа к данным фикстур в ваших тестовых случаях вы должны указать одно из следующего в вашем классе, производном от ActiveSupport::TestCase:
-
для полного включения инициализированных фикстур (включить альтернативные методы №1 и №2 выше)
self.use_instantiated_fixtures = true
-
создать только хеш для фикстур, не «находить» каждый экземпляр (включить только альтернативный метод №1)
self.use_instantiated_fixtures = :no_instances
Использование любого из этих альтернативных методов влечёт за собой снижение производительности, так как данные фикстур должны быть полностью пройдены в базе данных для создания хеша фикстур и/или переменных экземпляров. Это дорого для больших наборов данных фикстур.
Динамические фикстуры с
Иногда вас интересует не столько содержание фикстур, сколько объём. В таких случаях вы можете смешать ERB с вашими файлами фикстур YAML, чтобы создать множество фикстур для нагрузочного тестирования, например:
<% 1.upto(1000) do |i| %> fix_<%= i %>: id: <%= i %> name: guy_<%= i %> <% end %>
Это создаст 1000 очень простых фикстур.
Используя ERB, вы также можете вставлять динамические значения в ваши фикстуры с помощью вставок, таких как <%= Date.today.strftime("%Y-%m-%d") %>. Однако это функция, которую следует использовать с осторожностью. Суть фикстур — в том, что они представляют собой стабильные единицы предсказуемых образцовых данных. Если вы чувствуете, что вам нужно вставлять динамические значения, возможно, вам следует пересмотреть, правильно ли ваш приложение тестируется. Поэтому динамические значения в фикстурах следует рассматривать как признак плохой архитектуры.
Вспомогательные методы, определённые в фикстуре, недоступны в других фикстурах, чтобы предотвратить нежелательные межтестовые зависимости. Методы, используемые несколькими фикстурами, должны быть определены в модуле, включённом в ActiveRecord::FixtureSet.context_class.
-
определить вспомогательный метод в
test_helper.rbmodule FixtureFileHelpers def file_sha(path) OpenSSL::Digest::SHA256.hexdigest(File.read(Rails.root.join('test/fixtures', path))) end end ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers -
использовать вспомогательный метод в фикстуре
photo: name: kitten.png sha: <%= file_sha 'files/kitten.png' %>
Транзакционные тесты
Тестовые случаи могут использовать begin+rollback для изоляции своих изменений в базе данных вместо того, чтобы удалять+вставлять для каждого тестового случая.
class FooTest < ActiveSupport::TestCase
self.use_transactional_tests = true
test "godzilla" do
assert_not_empty Foo.all
Foo.destroy_all
assert_empty Foo.all
end
test "godzilla aftermath" do
assert_not_empty Foo.all
end
end
Если вы предварительно загрузили свою тестовую базу данных со всеми данными фикстур (вероятно, выполнив bin/rails db:fixtures:load) и используете транзакционные тесты, то вы можете опустить все объявления фикстур в своих тестовых случаях, так как все данные уже там, и каждый случай откатывает свои изменения.
Для использования инициализированных фикстур с предварительно загруженными данными установите self.pre_loaded_fixtures в true. Это предоставит доступ к данным фикстур для каждой таблицы, которая была загружена через фикстуры (в зависимости от значения use_instantiated_fixtures).
Когда не следует использовать транзакционные тесты:
-
Вы проверяете, правильно ли работает транзакция. Вложенные транзакции не коммитируются, пока не коммитируются все родительские транзакции, особенно транзакция фикстур, которая начинается в настройке и отменяется в завершении. Таким образом, вы не сможете проверить результаты вашей транзакции, пока Active Record не будет поддерживать вложенные транзакции или точки сохранения (на стадии разработки).
-
Ваша база данных не поддерживает транзакции. Каждая база данных Active Record поддерживает транзакции, за исключением MySQL MyISAM. Используйте InnoDB, MaxDB или NDB вместо этого.
Расширенные фикстуры
Фикстуры, которые не указывают ID, получают некоторые дополнительные функции:
-
Стабильные, автоматически сгенерированные ID
-
Ссылки на метки для ассоциаций (belongs_to, has_one, has_many)
-
Ассоциации HABTM как списки в строке
Существуют некоторые дополнительные расширенные возможности, даже если ID указан:
-
Автозаполнение столбцов временных меток
-
Интерполяция меток фикстур
-
Поддержка значений по умолчанию YAML
Стабильные, автоматически сгенерированные ID
Вот пример фикстуры обезьяны:
george: id: 1 name: George the Monkey reginald: id: 2 name: Reginald the Pirate
Каждая из этих фикстур имеет два уникальных идентификатора: один для базы данных и один для человека. Почему бы нам не сгенерировать первичный ключ вместо этого? Хеширование метки каждой фикстуры даёт постоянный ID:
george: # generated id: 503576764 name: George the Monkey reginald: # generated id: 324201669 name: Reginald the Pirate
Active Record обращается к классу модели фикстуры, определяет правильный первичный ключ и генерирует его непосредственно перед вставкой фикстуры в базу данных.
Сгенерированный ID для данной метки постоянный, поэтому мы можем определить ID любой фикстуры без загрузки чего-либо, если знаем метку.
Ссылки на метки для ассоциаций (belongs_to, has_one, has_many)
Указание внешних ключей в фикстурах может быть очень хрупким, не говоря уже о сложности чтения. Поскольку Active Record может определить ID любой фикстуры по её метке, вы можете указать FK по метке вместо ID.
belongs_to
Давайте разделим обезьян и пиратов.
### in pirates.yml reginald: id: 1 name: Reginald the Pirate monkey_id: 1 ### in monkeys.yml george: id: 1 name: George the Monkey pirate_id: 1
Добавьте ещё несколько обезьян и пиратов, разбейте это на несколько файлов, и становится довольно сложно отслеживать, что происходит. Давайте используем метки вместо ID:
### in pirates.yml reginald: name: Reginald the Pirate monkey: george ### in monkeys.yml george: name: George the Monkey pirate: reginald
Все понятно. Active Record обращается к классу модели фикстуры, находит все belongs_to ассоциации и позволяет вам указать целевую метку для ассоциации (обезьяна: джордж), а не целевой ID для FK (monkey_id: 1).
Полиморфный belongs_to
Поддержка полиморфных отношений немного сложнее, так как Active Record должен знать, к какому типу относится ваша ассоциация. Что-то вроде этого должно быть знакомо:
### in fruit.rb belongs_to :eater, polymorphic: true ### in fruits.yml apple: id: 1 name: apple eater_id: 1 eater_type: Monkey
Можно ли сделать лучше? Конечно!
apple: eater: george (Monkey)
Просто укажите полиморфный целевой тип, и Active Record позаботится о своём.
has_and_belongs_to_many или has_many :through
Time, чтобы дать нашей обезьяне фрукты.
### in monkeys.yml george: id: 1 name: George the Monkey ### in fruits.yml apple: id: 1 name: apple orange: id: 2 name: orange grape: id: 3 name: grape ### in fruits_monkeys.yml apple_george: fruit_id: 1 monkey_id: 1 orange_george: fruit_id: 2 monkey_id: 1 grape_george: fruit_id: 3 monkey_id: 1
Давайте избавимся от фикстуры fruits_monkeys.yml.
### in monkeys.yml george: id: 1 name: George the Monkey fruits: apple, orange, grape ### in fruits.yml apple: name: apple orange: name: orange grape: name: grape
Ура! Больше нет файла fruits_monkeys.yml. Мы указали список фруктов для Джорджа в фикстуре, но мы могли бы точно так же указать список обезьян для каждого фрукта. Как и в belongs_to, Active Record обращается к классу модели фикстуры и обнаруживает has_and_belongs_to_many ассоциации.
Автозаполнение Timestamp столбцов
Если ваша таблица/модель указывает любые стандартные столбцы временных меток Active Record (created_at, created_on, updated_at, updated_on), они будут автоматически установлены на Time.now.
Если вы задали определённые значения, они останутся прежними.
Интерполяция метки фикстуры
Метка текущей фикстуры всегда доступна как значение столбца:
geeksomnia: name: Geeksomnia's Account subdomain: $LABEL email: $LABEL@email.com
Также, иногда (например, при переносе старых фикстур таблиц присоединения) вам нужно будет получить идентификатор для заданной метки. ERB на помощь:
george_reginald: monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %> pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %>
Поддержка значений по умолчанию YAML
Вы можете задавать и повторно использовать значения по умолчанию в файле фикстур YAML. Это тот же приём, который используется в файле database.yml для указания значений по умолчанию:
DEFAULTS: &DEFAULTS created_on: <%= 3.weeks.ago.to_formatted_s(:db) %> first: name: Smurf <<: *DEFAULTS second: name: Fraggle <<: *DEFAULTS
Любая фикстура с меткой «DEFAULTS» безопасно игнорируется.
Помимо использования «DEFAULTS», вы также можете указать, какие фикстуры будут игнорироваться, установив «ignore» в разделе «_fixture».
# users.yml
_fixture:
ignore:
- base
# or use "ignore: base" when there is only one fixture that needs to be ignored.
base: &base
admin: false
introduction: "This is a default description"
admin:
<<: *base
admin: true
visitor:
<<: *base В приведённом выше примере «base» будет проигнорирован при создании фикстур. Это можно использовать для общих атрибутов, наследуемых из других фикстур.
Настройка класса модели фикстуры
Можно непосредственно в файле YAML установить класс модели фикстуры. Это полезно, когда фикстуры загружаются за пределами тестов и set_fixture_class недоступен (например, при выполнении bin/rails db:fixtures:load).
_fixture: model_class: User david: name: David
Любые фикстуры с меткой «_fixture» безопасно игнорируются.
Константы
- MAX_ID
Атрибуты
Публичные методы класса
# File activerecord/lib/active_record/fixtures.rb, line 536 def cache_fixtures(connection, fixtures_map) cache_for_connection(connection).update(fixtures_map) end
# File activerecord/lib/active_record/fixtures.rb, line 520 def cache_for_connection(connection) @@all_cached_fixtures[connection] end
# File activerecord/lib/active_record/fixtures.rb, line 528
def cached_fixtures(connection, keys_to_fetch = nil)
if keys_to_fetch
cache_for_connection(connection).values_at(*keys_to_fetch)
else
cache_for_connection(connection).values
end
end # File activerecord/lib/active_record/fixtures.rb, line 589 def context_class @context_class ||= Class.new end
Базовый класс для контекстов оценки, используемых в фикстурах ERB.
# File activerecord/lib/active_record/fixtures.rb, line 555
def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block)
fixture_set_names = Array(fixture_set_names).map(&:to_s)
class_names = ClassCache.new class_names, config
# FIXME: Apparently JK uses this.
connection = block_given? ? block : lambda { ActiveRecord::Base.connection }
fixture_files_to_read = fixture_set_names.reject do |fs_name|
fixture_is_cached?(connection.call, fs_name)
end
if fixture_files_to_read.any?
fixtures_map = read_and_insert(
fixtures_directory,
fixture_files_to_read,
class_names,
connection,
)
cache_fixtures(connection.call, fixtures_map)
end
cached_fixtures(connection.call, fixture_set_names)
end # File activerecord/lib/active_record/fixtures.rb, line 524 def fixture_is_cached?(connection, table_name) cache_for_connection(connection)[table_name] end
# File activerecord/lib/active_record/fixtures.rb, line 580
def identify(label, column_type = :integer)
if column_type == :uuid
Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
else
Zlib.crc32(label.to_s) % MAX_ID
end
end Возвращает согласованный, независимый от платформы идентификатор для label. Integer идентификаторы — значения меньше 2^30. UUID — это RFC 4122 версия 5 SHA-1 хэши.
# File activerecord/lib/active_record/fixtures.rb, line 549
def instantiate_all_loaded_fixtures(object, load_instances = true)
all_loaded_fixtures.each_value do |fixture_set|
instantiate_fixtures(object, fixture_set, load_instances)
end
end # File activerecord/lib/active_record/fixtures.rb, line 540
def instantiate_fixtures(object, fixture_set, load_instances = true)
return unless load_instances
fixture_set.each do |fixture_name, fixture|
object.instance_variable_set "@#{fixture_name}", fixture.find
rescue FixtureClassNotFound
nil
end
end # File activerecord/lib/active_record/fixtures.rb, line 650 def initialize(_, name, class_name, path, config = ActiveRecord::Base) @name = name @path = path @config = config self.model_class = class_name @fixtures = read_fixture_files(path) @table_name = model_class&.table_name || self.class.default_fixture_table_name(name, config) end
# File activerecord/lib/active_record/fixtures.rb, line 516 def reset_cache @@all_cached_fixtures.clear end
Публичные методы экземпляра
# File activerecord/lib/active_record/fixtures.rb, line 662 def [](x) fixtures[x] end
# File activerecord/lib/active_record/fixtures.rb, line 666 def []=(k, v) fixtures[k] = v end
# File activerecord/lib/active_record/fixtures.rb, line 670 def each(&block) fixtures.each(&block) end
# File activerecord/lib/active_record/fixtures.rb, line 674 def size fixtures.size end
# File activerecord/lib/active_record/fixtures.rb, line 680
def table_rows
# allow specifying fixtures to be ignored by setting `ignore` in `_fixture` section
fixtures.except!(*ignored_fixtures)
TableRows.new(
table_name,
model_class: model_class,
fixtures: fixtures,
).to_hash
end Возвращает хэш строк для вставки. Ключ — таблица, значение — список строк для вставки в эту таблицу.
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.