首页 > 解决方案 > 如何测试复合 Rails 模型范围?

问题描述

考虑以下模型:

class Model < ActiveRecord::Base
  scope :a,       -> () { where(a: 1) }
  scope :b,       -> () { where(b: 1) }
  scope :a_or_b,  -> () { a.or(b) }
end

现在要正确测试每个范围,我至少会为每个可能的变量提供一个匹配的示例和一个不匹配的示例。像这样的东西:

RSpec.describe Model do
  describe "scopes" do
    describe ".a" do
      subject { Model.a }
      let!(:with_a_0) { create(:model, a: 0) }
      let!(:with_a_1) { create(:model, a: 1) }

      it { should_not include(with_a_0) }
      it { should     include(with_a_1) }
    end

    describe ".b" do
      subject { Model.b }
      let!(:with_b_0) { create(:model, b: 0) }
      let!(:with_b_1) { create(:model, b: 1) }

      it { should_not include(with_b_0) }
      it { should     include(with_b_1) }
    end

    describe ".a_or_b" do
      subject { Model.a_or_b }
      let!(:with_a_0_and_b_0) { create(:model, a: 0, b: 0) }
      let!(:with_a_0_and_b_1) { create(:model, a: 0, b: 1) }
      let!(:with_a_1_and_b_0) { create(:model, a: 1, b: 0) }
      let!(:with_a_1_and_b_1) { create(:model, a: 1, b: 1) }

      it { should_not include(with_a_0_and_b_0) }
      it { should     include(with_a_0_and_b_1) }
      it { should     include(with_a_1_and_b_0) }
      it { should     include(with_a_1_and_b_1) }
    end
  end
end

但是感觉就像我在重新测试.a.b.a_or_b测试中,如果我再次创作它,用另一个范围,它会变得越来越大。

处理这个问题的明智方法是什么?

另外:这是单元测试还是集成测试?

标签: ruby-on-railsunit-testingtestingrspecintegration-testing

解决方案


这是一个艰难的。我想说你必须在全面覆盖和你的规格可读性之间找到一个亚里士多德的平均值。测试影响示波器行为方式的所有可能状态组合可能是不合理的。

它也不是真正的单元测试,因为它与 DB 层耦合。

我的方法是这样的:

不要测试简单的范围(如ab在您的示例中),相信 AR 已经过良好测试,让您的范围名称清晰并保留它。

在更复杂的范围内,您可以颠倒编写测试的方式:创建一次样本,然后为不同的范围定义期望(并使样本名称非常清晰,就像您已经在做的那样)。

这将稍微减小规范的大小,并且更容易阅读这些规范。但是,如果您想全面覆盖每个范围,它仍然会增加规格大小。

RSpec.describe Model do
  describe "scopes" do
    let!(:with_a_0_and_b_0) { create(:model, a: 0, b: 0) }
    let!(:with_a_0_and_b_1) { create(:model, a: 0, b: 1) }
    let!(:with_a_1_and_b_0) { create(:model, a: 1, b: 0) }
    let!(:with_a_1_and_b_1) { create(:model, a: 1, b: 1) }

    it { expect(described_class.a).to include(with_a_1_and_b_1).and include(with_a_1_and_b_1) }
    it { expect(described_class.a).not_to include(with_a_0_and_b_0) }

    # you get the idea

为了使这个^更具可读性,我建议创建一个自定义 matcher,您可以像这样使用:

  it do 
    expect(described_class.a)
      .to retrieve(with_a_1_and_b_0, with_a_1_and_b_1)
      .and not_retrieve(with_a_0_and_b_0, with_a_0_and_b_1)
  end

推荐阅读