代码之家  ›  专栏  ›  技术社区  ›  Community wiki

如果我调用Factory.build以使我的控制器测试快速,我如何让Factory Girl永远不会访问数据库?

  •  21
  • Community wiki  · 技术社区  · 1 年前

    我正在寻求让我的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、存根并在每次测试的基础上手动填充对象值是“正确的”做法。

    2 回复  |  直到 13 年前
        1
  •  17
  •   Fire Emblem    13 年前

    好吧,我想我会回答我自己的问题。我认为这是正确的答案,也许其他人可以从中学习,因为我不得不花几个小时来学习。

    以下是我如何获得2000%(或20倍)的速度提升:

    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秒内运行!之前,运行所有3个测试需要1.4秒。

    Factory_girl只是一个非常慢的图书馆。我不知道它到底在做什么,但它没有被正确地描述。

    是的,我知道它做的远不止简单 MyClass.new 语句。。。但是,即使对于像Ruby这样速度较慢的脚本语言,其性能也比基本类实例化慢很多数量级。它需要进行一些大规模的优化,以便 Factory.build(:my_class) 更符合 MyClass.new

    我建议Factory_girl的实现者尝试获得它,这样它的开销就不会比基本的 MyClass.new 调用(不包括无法避免的数据库开销)。它应该提供一种很好的方法来构建对象,并且您不应该为了获得这一好处而支付20倍的性能损失。这不是一个可以接受的权衡。

    这真的太糟糕了,因为 Factory.build 如果你有 render_views 在控制器规格内部打开。应该有很大的动机来纠正这一点。

    同时,只需使用基本的Ruby/Rails类。我想你会惊讶于它们的速度。。。。

        2
  •  0
  •   Isaac Betesh    11 年前

    我遇到了与@FireEmblem相同的问题,最终将问题缩小到 FactoryGirl.build FactoryGirl.stub 并没有让事情变得更好。

    我终于意识到这是因为我的一个模型有验证逻辑,当某个字段存在时,它会发出HTTP请求。工厂对这个领域进行了评估,所以从外表上看,FactoryGirl似乎放慢了我的测试速度。事实上是这样,但这只是因为它触发了HTTP请求。从我的一个工厂中删除一行消除了HTTP请求,使性能提高了60倍。