【问题标题】:Rails: Testing named scopes with RSpecRails:使用 RSpec 测试命名范围
【发布时间】:2011-09-23 01:04:01
【问题描述】:

我是测试 Rails Web 应用程序和 RSpec 的新手。我使用遗留代码,需要添加测试。那么使用 RSpec 测试查找器和命名范围的最佳方法是什么?

我在 Google 中找到了一些方法,但它们并不理想。 例如:

http://paulsturgess.co.uk/articles/show/93-using-rspec-to-test-a-named_scope-in-ruby-on-rails

it "excludes users that are not active" do
    @user = Factory(:user, :active => false)
    User.active.should_not include(@user)
end

http://h1labs.com/notebook/2008/8/21/testing-named-scope-with-rspec

it "should have a published named scope that returns ..." do
  Post.published.proxy_options.should == {:conditions => {:published => true}}
end

我在“Rail Test Prescriptions”中找到了最佳方法(恕我直言):

should_match_find_method :active_only { :active == true }

where should_match_find_method 自定义辅助方法

【问题讨论】:

  • 范围测试真的是经验性的,我坚持你在这里公开的第一种方法
  • 我同意。第一种方法有什么问题?它规范了真实的行为,而不是检查(实际上只是重写)可能有缺陷的配置参数。
  • @RobDavis “可能有缺陷的配置参数”+1

标签: ruby-on-rails ruby testing rspec


【解决方案1】:

第一种方法的问题在于它实际上是查询数据库。这是缓慢且不必要的。如果您不介意,您可以安全地使用第一种方法。第二种方法既快速又清晰,因此我会推荐它。

【讨论】:

  • 您确定不需要在模型规范中进行查询吗?我可以理解不在控制器中执行此操作或查看规范 - 模拟并将其存根。但我认为您想在这里测试模型及其与数据库的关系。
  • IMO 在测试命名范围时没有必要。您应该有大量其他实际从数据库中读取的测试。
  • 范围是查询数据库以获取真实对象的一个​​领域。我喜欢在适当的地方进行模拟,但只有最简单的范围才能与模拟一起使用。一旦你开始加入或分组,它们就会崩溃,我已经制定了一个规则,即针对真实的后备存储测试范围。恕我直言,像第二个和第三个示例这样的基于配置的匹配器很容易出错。也就是说,它们很可能会出现与您的代码相同的错误。
  • 模型获得工厂,控制器获得模拟。在控制器中我们关心我们的状态和响应,在模型中我们关心数据。确实,我们不应该在 find 之类的东西中进行测试,但是范围似乎是一个完全合理的测试对象。
【解决方案2】:

RSpec 的创建者最近发布了他认为Validations are behavior, associations are structure 的博客。换句话说,他发现不应该直接测试关联(和范围)。这些测试将根据您想要的行为进行。

换句话说,目前的观点是没有必要直接测试每个范围,因为您将通过测试应用程序的行为来涵盖这些关联。

【讨论】:

  • 他在那篇文章中没有提到范围,但是,你为什么将它们与关联混为一谈?范围可能比关联更复杂且更容易出错,我认为应该进行测试。由于您调用了强大的创建者here,他建议如果一个范围属于行为的范畴(如果不属于,为什么存在?),那么应该指定它。
  • 我并不反对不应测试范围。他们绝对应该。只是不直接;范围的测试应该通过测试应用程序的行为来驱动。我认为范围类似于关联,因为两者都可以具有复杂的选项,例如连接、包含的模型、位置/分组/具有等等。无论如何,这对我来说是有意义的。
  • 我认为当你有构建作用域的逻辑时,特别是如果该逻辑变得复杂,围绕它编写单元测试会很有用。
【解决方案3】:

来自https://coderwall.com/p/hc8ofa/testing-rails-model-default_scope-with-rspec

  • 没有数据库查询
  • 无需在结构中表示查询

例子:

class Trip < ActiveRecord::Base
  default_scope { order(departure: :asc) }
  ...
end

RSpec.describe Trip, type: :model do
  it "applies a default scope to collections by departure ascending" do
    expect(Trip.all.to_sql).to eq Trip.all.order(departure: :asc).to_sql
  end
end

【讨论】:

    【解决方案4】:

    David Chelimsky 测试范围(更新)

    David Chelimsky example, (linked by Sam Peacey's comment), modernised.

    # app/models/user.rb
    
    class User < ActiveRecord::Base
      scope :admins, -> { where(admin: true) }
    end
    
    # spec/models/user_spec.rb
    
    RSpec.describe User, type: :model do
      describe ".admins" do
        it "includes users with admin flag" do
          admin = User.create!(admin: true)
          expect(User.admins).to include(admin)
        end
    
        it "excludes users without admin flag" do
          non_admin = User.create(admin: false)
          expect(User.admins).not_to include(non_admin)
        end
      end
    end
    

    这会产生更“规范”的输出(使用 --format 文档时):

    User
      .admins
        includes users with admin flag
        excludes users without admin flag
    

    关于这个答案的起源的说明:

    当时的 RSpec 负责人 David Chelimsky 回答了这个问题,而 Sam Peacey 的链接获得的投票数比实际答案多。由于他正在回复某人并在电子邮件链中编辑他们的答案,因此很难找到和关注答案。这个答案清理并更新了 RSpec 代码,我猜他今天会写的。

    【讨论】:

      猜你喜欢
      • 2014-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-12
      • 1970-01-01
      • 2013-08-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多