Я пытаюсь сделать тесты 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, заглушек и заполнение значений объектов вручную на основе каждого теста - это "правильная" вещь, если вы хотите избежать приспособлений, а также получить высокую производительность при выполнении тестов.