我正在寻求让我的Rails测试更快。我只有520个测试,但它们在bash中运行需要62秒,在Rubymine中运行需要82秒。
作为一个典型控制器测试的例子,我使用以下代码以@user的身份登录_in,并在CommentsController中为我的RSpec控制器测试创建基本的@comment:
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()
并在我的父控制器中截取所有的before过滤器。然而,我的rspec日志仍然显示,当我事后检查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)
块对测试性能没有任何影响。正如我所发现的
Factory.build()
仍将内部调用
Factory.create()
关于儿童协会。
这是一个
之前(:每个)
块,有效地删除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到.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 'Banoo.Smith@gmail.com'
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
还有有趣的基准。拨打电话平均需要.1到.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)
即使是
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很遗憾并不是。
我下面的解决方案使用了基本的对象实例化和存根,测试继续通过。我认为,如果你想在运行测试时避免固定装置并获得高性能,那么使用基本的Ruby、存根并在每次测试的基础上手动填充对象值是“正确的”做法。