Подтвердить что ты не робот

Как я могу получить Factory Girl, чтобы НИКОГДА не попал в базу данных, если я вызываю Factory.build, чтобы мои контрольные тесты FAST?

Я пытаюсь сделать тесты Rails быстрее. У меня всего 520 тестов, но они занимают 62 секунды для запуска в bash и 82 секунды для запуска в Rubymine.

В качестве примера типичного теста контроллера я использовал этот код для sign_in как @user и создал базовый @comment в контроллере комментариев для тестов контроллера RSpec:

before(:each) do
  @user = Factory.create(:user)
  sign_in @user

  @comment = Factory.create(:comment)
end

Как вы понимаете... это медленно. Он создает @user, но также создает ассоциации для этого пользователя. То же самое для @comment.

Итак, я думал, что вызов Factory.build(:user) решит его... но я получаю странные ошибки. Например, current_user возвращает nil.

Итак... Я решил использовать Factory.build() и вырезать все фильтры перед моим родительским контроллером. Тем не менее, мой журнал rspec все еще говорит, что TON вставок попадает в базу данных, когда я проверяю журнал RSPec после этого (мы говорим о сотнях строк кода всего за 3 теста!)

  before(:each) do
    @user = Factory.build(:user)
    #sign_in @user

    controller.stub(:authenticate_user!) #before_filter
    controller.stub(:add_secure_model_data) #before_filter
    controller.stub(:current_user).and_return(@user)

    @comment = Factory.build(:comment)
  end

Печальный факт: вышеупомянутый блок before(:each) имеет эффект ZERO на производительность теста. Как я выяснил, вызов Factory.build() будет по-прежнему внутренне вызывать Factory.create() дочерних ассоциаций.

Вот блок before(:each), который эффективно удаляет мусор, созданный в журнале RSpec. Это дало мне повышение производительности на 35-40%

  before(:each) do
    @user = Factory.build(:user, :role => Factory.build(:role))
    #sign_in @user

    controller.stub(:authenticate_user!)
    controller.stub(:add_secure_model_data)
    controller.stub(:current_user).and_return(@user)

    # both of these are still super slow. WTF?!
    @site_update = Factory.build(:site_update, :id => 5, :author => Factory.build(:user, :role => Factory.build(:role)))

    @comment = Factory.build(:comment,
                             :author => Factory.build(:user, :role => Factory.build(:role)),
                             :commentable => @site_update)
  end

Это заставляет тесты работать быстрее, но это также уродливо, как грех. Мы не можем серьезно написать это для каждого теста... не так ли? Это орехи. Я этого не делаю.

Я также хочу отметить, что любая из этих строк Factory.build() по-прежнему занимает около 15 секунд, даже если они НЕ попадают в базу данных!

Запуск только 3 тестов по-прежнему приводит к примерно 0,3... 0,35 секунды времени, затраченного на тестирование factory_girl PER! Я думаю, что это абсолютно неприемлемо. Если вы удалите строки Factory.build(), тесты пройдут через 0,00001 секунд.

Я думаю, что жюри: factory_girl - одна очень медленная библиотека. Единственное решение не использовать его?

Вот мой factories.rb:

Factory.define :role do |f|
  f.name "Admin"
end

Factory.define :user do |f|
  f.first_name "Banoo"
  f.last_name "Smith"
  f.sequence(:email) { |n| "Banoo.Smith#{n}@gmail.com" }
  f.password "secretpassword"
  f.association :role
end

Factory.define :admin do |f|
  f.first_name "Banoo"
  f.last_name "Smith"
  f.sequence(:email) { |n| "admin#{n}@gmail.com" }
  f.password "secretpassword"
  f.association :role
end

Factory.define :course_provider do |f|
  f.first_name "Josh"
  f.last_name "Bolson"
  f.sequence(:email) { |n| "josh.bolson#{n}@gmail.com" }
  f.password "secretpassword"
  f.association :role
end

Factory.define :director do |f|
  f.first_name "Director"
  f.last_name "Dude"
  f.sequence(:email) { |n| "director#{n}@gmail.com" }
  f.password "secretpassword"
  f.association :role
end

Factory.define :instructor do |f|
  f.first_name "Instructor"
  f.last_name "Dude"
  f.sequence(:email) { |n| "instructor#{n}@gmail.com" }
  f.password "secretpassword"
  f.association :role
end

Factory.define :trainee do |f|
  f.first_name "Trainee"
  f.last_name "Dude"
  f.sequence(:email) { |n| "trainee#{n}@gmail.com" }
  f.password "secretpassword"
  f.association :role
end

Factory.define :private_message do |f|
  f.subject "Subject"
  f.content "content"
  f.is_deleted_by_sender false
  f.association :sender, :factory => :user
end

Factory.define :recipient do |f|
  f.is_read false
  f.is_deleted false
  f.association :receiver, :factory => :user
  f.association :private_message
end

Factory.define :course_template do |f|
  f.name "name"
  f.description "description"
  f.association :course_provider
end

Factory.define :site_update do |f|
  f.subject "Subject"
  f.intro "intro"
  f.content "content"
  f.association :author, :factory => :user
end

Factory.define :comment do |f|
  f.content "content"
  f.association :author, :factory => :user
  f.association :commentable, :factory => :site_update
end

Factory.define :country do |f|
  f.name "Liberty"
end

Factory.define :province do |f|
  f.name "Freedom"
  f.association :country
end

Factory.define :payment_plan do |f|
  f.name "name"
  f.monthly_amount 79
  f.audience "Enterprises"
  f.active_courses "500-2000"
end

Factory.define :company do |f|
  f.name "name"
  f.phone_number "455-323-2132"
  f.address "address"
  f.postal_code "N7G-5F4"
  f.association :province
  f.association :payment_plan
end

Factory.define :company_user do |f|
  f.first_name "Dan"
  f.last_name "Grayson"
  f.sequence(:email) { |n| "dan.grayson#{n}@gmail.com" }
  f.password "secretpassword"
  f.association :role
  f.association :company
end

Factory.define :course do |f|
  f.notes "notes"
  f.difficulty 100
  f.association :course_template
  f.association :instructor, :factory => :company_user
end

Factory.define :study_group do |f|
  f.name "name"
end

Factory.define :help_category do |f|
  f.name "name"
end

Factory.define :help_document do |f|
  f.question "question"
  f.content "content"
  f.association :category, :factory => :help_category
end

Factory.define :tag do |f|
  f.name "name"
end

Factory.define :partial_mapping do |f|
  f.from_suffix "ing"
  f.to_suffix "ing"
end

Factory.define :newsletter do |f|
  f.subject "subject"
  f.content "content"
end

Factory.define :press_contact do |f|
  f.full_name "Banoo Smith"
  f.email '[email protected]'
  f.phone_number "455-323-2132"
  f.address "address"
  f.postal_code "N9B-3W5"
  f.association :province
end

Factory.define :press_release do |f|
  f.headline "Headline"
  f.origin "origin"
  f.intro "intro"
  f.body "body"
  f.association :contact, :factory => :press_contact
end

Factory.define :theme do |f|

end

И интересный бенчмарк. Обычно требуется от 0,1 до 0,14 секунды, чтобы позвонить на Factory.create(:user):

$ rails runner 'Benchmark.bm {|x| x.report { 100.times { Factory.create(:user) } } }' 
      user     system      total        real
  9.940000   0.080000  10.020000 ( 14.872736)

Даже a Factory.build(:user) берет навсегда... и это при включенном :default_strategy => :build!

$ rails runner 'Benchmark.bm {|x| x.report { 100.times { Factory.build(:user) } } }'
      user     system      total        real
  9.350000   0.030000   9.380000 ( 11.798339)

Очевидно, это свидетельствует о том, что с factory_girl что-то не так. Решение состоит в том, чтобы избавиться от него или убедиться, что оно использует Factory.build. Это ответ.

Поскольку я в основном решил свою собственную проблему, мне интересно, почему Factory_girl так популярен, и почему это "общая мудрость"? Можно объективно заключить, что любые выгоды могут быть получены при использовании Factory Girl - и там много приятных вещей - это не стоит затрат на производительность. Я уверен, что лучше создать драгоценный камень Factory, который будет намного более реалистичным... но, к сожалению, заводская девушка и, к сожалению, не она.

В моем решении ниже используются базовые экземпляры объектов и заглушки, и тесты продолжают проходить. Я думаю, что использование базового Ruby, заглушек и заполнение значений объектов вручную на основе каждого теста - это "правильная" вещь, если вы хотите избежать приспособлений, а также получить высокую производительность при выполнении тестов.

4b9b3361

Ответ 1

Ну, я думаю, я отвечу на свой вопрос. Я думаю, это правильный ответ, и, возможно, другие могут учиться на нем, поскольку мне пришлось потратить несколько часов, чтобы изучить его.

Вот как я получил улучшение скорости на 2000% (или 20x):

before(:each) do
  @user = User.new
  controller.stub(:authenticate_user!)
  controller.stub(:current_user).and_return(@user)
  controller.stub(:add_secure_model_data)

  @site_update = SiteUpdate.new
  @comment = Comment.new
end

Решение просто не использовать фабрики любого типа для тестов контроллера (и, возможно, других видов тестов). Я предлагаю использовать только Factory, когда это слишком большая боль в заднице, чтобы сделать иначе.

Все 3 теста теперь выполняются через 0,07 секунды! До того, как это было 1,4 секунды, чтобы запустить все 3 теста.

Factory_girl - просто ужасно медленная библиотека. Я не знаю, что он делает, но он не профилирован должным образом.

Да, я знаю, что это делает намного больше, чем простые инструкции MyClass.new... но даже для более медленного языка сценариев, такого как Ruby, производительность на много порядков медленнее, чем базовая реализация класса. Он должен подвергнуться некоторой массовой оптимизации, так что Factory.build(:my_class) приводится в соответствие с MyClass.new

Я бы предложил разработчикам Factory_girl попробовать и получить его так, чтобы накладные расходы были не намного медленнее, чем базовый вызов MyClass.new (исключая издержки базы данных... чего нельзя избежать). Он должен обеспечить хороший способ создания объектов, и вам не нужно платить штраф за 20 очков, чтобы получить эту выгоду. Это не приемлемый компромисс.

Это все очень плохо, потому что Factory.build будет приятным в контроллерах, если вы включили render_views внутри своих спецификаций контроллера. Должна быть важная мотивация, чтобы исправить это.

Тем временем просто используйте базовые классы Ruby/Rails. Я думаю, вы будете удивлены, насколько быстро они на самом деле...

Ответ 2

У меня была та же проблема, что и @FireEmblem, и в конечном итоге сузила проблему до FactoryGirl.build. FactoryGirl.stub не улучшал ситуацию.

Я, наконец, понял это, потому что у одной из моих моделей была логика проверки, которая сделала HTTP-запрос, когда какое-то поле присутствовало. factory поместите значение в это поле, так что снаружи это выглядело так, как FactoryGirl замедлял мои тесты. На самом деле это было, но только потому, что оно вызвало HTTP-запрос. Удаление одной строки с одной из моих фабрик устранило HTTP-запрос, что привело к повышению производительности на 60%.