Spec-Zone.ru › Ruby on Rails 6.1

класс 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.rb

    module FixtureFileHelpers
      def file_sha(path)
        Digest::SHA2.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).

Когда не следует использовать транзакционные тесты:

  1. Вы тестируете, работает ли транзакция правильно. Вложенные транзакции не коммитируются до тех пор, пока не коммитируются все родительские транзакции, в частности, транзакция фикстур, которая начинается в setup и отменяется в teardown. Таким образом, вы не сможете проверить результаты вашей транзакции, пока Active Record не будет поддерживать вложенные транзакции или точки сохранения (в разработке).

  2. Ваша база данных не поддерживает транзакции. Каждая база данных 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

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

Давайте сделаем фикстуру HABTM незаметной.

### 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_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 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

Атрибуты

config[R]
fixtures[R]
ignored_fixtures[R]
model_class[R]
name[R]
table_name[R]

Методы публичного класса

cache_fixtures(connection, fixtures_map) Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 536
def cache_fixtures(connection, fixtures_map)
  cache_for_connection(connection).update(fixtures_map)
end
cache_for_connection(connection) Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 520
def cache_for_connection(connection)
  @@all_cached_fixtures[connection]
end
cached_fixtures(connection, keys_to_fetch = nil) Показать исходный код
# 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
context_class() Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 597
def context_class
  @context_class ||= Class.new
end

Базовый класс для контекстов вычислений, используемых плагинами ERB fixtures.

create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block) Показать исходный код
# 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
fixture_is_cached?(connection, table_name) Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 524
def fixture_is_cached?(connection, table_name)
  cache_for_connection(connection)[table_name]
end
identify(label, column_type = :integer) Показать исходный код
# 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.

instantiate_all_loaded_fixtures(object, load_instances = true) Показать исходный код
# 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
instantiate_fixtures(object, fixture_set, load_instances = true) Показать исходный код
# 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
new(_, name, class_name, path, config = ActiveRecord::Base) Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 654
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
reset_cache() Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 516
def reset_cache
  @@all_cached_fixtures.clear
end
signed_global_id(fixture_set_name, label, column_type: :integer, **options) Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 588
def signed_global_id(fixture_set_name, label, column_type: :integer, **options)
  identifier = identify(label, column_type)
  model_name = default_fixture_model_name(fixture_set_name)
  uri = URI::GID.build([GlobalID.app, model_name, identifier, {}])

  SignedGlobalID.new(uri, **options)
end

Методы публичного экземпляра

[](x) Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 666
def [](x)
  fixtures[x]
end
[]=(k, v) Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 670
def []=(k, v)
  fixtures[k] = v
end
each(&block) Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 674
def each(&block)
  fixtures.each(&block)
end
size() Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 678
def size
  fixtures.size
end
table_rows() Показать исходный код
# File activerecord/lib/active_record/fixtures.rb, line 684
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,
    config: config,
  ).to_hash
end

Возвращает словарь строк для вставки. Ключ — таблица, значение — список строк для вставки в эту таблицу.

© 2004–2020 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API