【问题标题】:Speeding up associations in model specs with FactoryGirl - create vs build vs build_stubbed使用 FactoryGirl 加速模型规范中的关联 - create vs build vs build_stubbed
【发布时间】:2013-12-18 16:28:47
【问题描述】:

假设我有模型User 和Post,一个用户has_many 帖子和一个帖子belongs_to 一个用户。

当我为Post 编写规范时,我的第一反应是这样写:

before do
  @user = FactoryGirl.create :user
  @post = @user.posts.new(title: "Foo", content: "bar)
end

... tests for @post go here ...

但这会为每个测试创建一个新用户 - 访问数据库 - 这会减慢速度。有没有更好的方法可以加快我的测试速度并避免频繁访问数据库?

据我了解,我不能使用FactoryGirl.build :user,因为即使它不会访问数据库,关联也不会正常工作,因为@user 没有ID,所以@post.user不起作用(它返回nil。)

我可以使用FactoryGirl.build_stubbed :user,它创建了一个“假持久化”@user,它确实有一个 ID,但@post.user 仍然返回 nil。当我测试与关联相关的东西时,build_stubbed 是否比 build 有任何实际优势?

我想我可以使用build_stubbed 存根@post.user 所以它返回@user...有什么理由这可能是个坏主意吗?

或者我应该只使用create 并接受速度打击?

我能想到的唯一其他选择是在before(:all) 块中设置@user,这似乎是个坏主意。

以简洁明了的方式编写此类测试以避免进行过多的数据库查询的最佳方法是什么?

【问题讨论】:

    标签: ruby-on-rails unit-testing rspec factory-bot


    【解决方案1】:

    如果您不希望您的测试访问数据库,这就是您必须做的。

    before do
      @user = FactoryGirl.build_stubbed :user
      @post = FactoryGirl.build_stubbed :post
      @user.stub(:posts).and_return([@post])
      @post.stub(:user).and_return(@user)
    end
    

    注意:使用before(:all) 时要小心。它不会在事务中执行。因此,无论您在 before(:all) 中创建什么,都将留在数据库中,并可能与其他测试发生冲突

    关于FactoryGirl.build,它构建对象,但创建关联。

    例如:

    factory :user do
      association posts
    end
    
    FactoryGirl.build(:user) #this creates posts in the database even though you are only building the parent object(user)
    

    【讨论】:

      【解决方案2】:

      简答

      @user = FactoryGirl.build_stubbed(:user)
      @post = FactoryGirl.build_stubbed(:post, :user => @user)
      

      这将使@post.user 无需访问数据库即可工作。

      长答案

      我的建议是等待before 块,直到您确定需要它。相反,为每个单独的测试构建您需要的数据,并在找到时提取重复的方​​法或新工厂。

      另外,你真的需要在每个测试中引用用户吗?在每个测试中都提供@user 可以向其他开发人员表明它在任何地方都很重要。

      最后,假设在您的 post factory 中也声明了用户关联,当您执行 build_stubbed(:post) 时,您将自动获得一个有效的 post.user。

      【讨论】:

        【解决方案3】:

        很容易忘记create、build 和build_stubbed 之间的区别。这是针对处于相同情况的用户的快速参考(因为此页面在搜索结果中排名很高)。

        # Returns a User instance that's not saved (does not write to DB)
        user = build(:user)
        
        # Returns a saved User instance (writes to DB)
        user = create(:user)
        
        # Returns a hash of attributes that can be used to build a User instance
        attrs = attributes_for(:user)
        
        # Returns an object with all defined attributes stubbed out
        stub = build_stubbed(:user)
        
        # Passing a block to any of the methods above will yield the return object
        create(:user) do |user|
          user.posts.create(attributes_for(:post))
        end
        

        Source

        【讨论】:

          【解决方案4】:

          从工厂女孩的文件中,您可以像这样在post工厂中为user识别策略build:

          factory :post do
            association :user, factory: :user, strategy: :build
          end
          

          这样你就可以build 一个post 而不保存user

          post = build(:post)
          post.new_record?        # => true
          post.author.new_record? # => true
          

          【讨论】:

            【解决方案5】:

            快速解释差异: FactoryGirl.create 将为它创建新对象和关联(如果工厂有的话)。它们都将保存在数据库中。此外,它将触发模型和数据库验证。 保存工厂后将调用 after(:build) 和 after(:create) 回调。在工厂保存之前也会调用 before(:create)。

            FactoryGirl.build 不会保存对象,但如果工厂有关联,仍会向数据库发出请求。它只会触发关联对象的验证。 工厂建好后会调用 after(:build) 回调。

            FactoryGirl.build_stubbed 根本不调用数据库。它为一个对象创建和分配属性,使其表现得像一个实例化的对象。它提供了一个虚假的 id 和 created_at。关联(如果有)也将通过 build_stubbed 创建。它不会触发任何验证。

            阅读完整说明here

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2015-01-04
              • 2011-12-16
              • 2015-10-04
              • 2016-12-01
              • 1970-01-01
              • 2014-03-24
              • 1970-01-01
              相关资源
              最近更新 更多