RSpec的主题之间的区别是什么?什么时候应该使用它们?

时间:2016-07-18 12:49:05

标签: ruby unit-testing rspec

http://betterspecs.org/#subject包含有关subjectlet的一些信息。但是,我仍然不清楚它们之间的区别。此外,SO帖What is the argument against using before, let and subject in RSpec tests?表示最好不要使用subjectlet。我该去哪儿?我很困惑。

3 个答案:

答案 0 :(得分:97)

总结:RSpec的主题是一个特殊变量,指的是被测对象。期望可以隐式设置,支持单行示例。读者在一些惯用的案例中很清楚,但其他方面很难理解,应该避免。 RSpec的let变量只是懒惰的实例化(memoized)变量。它们并不像主题一样难以理解,但仍然可能导致纠结的测试,因此应谨慎使用。

主题

如何运作

主题是被测对象。 RSpec对该主题有明确的想法。它可能定义也可能不定义。如果是,RSpec可以在其上调用方法而无需明确引用它。

默认情况下,如果最外层示例组(describecontext块)的第一个参数是一个类,则RSpec会创建该类的实例并将其分配给主题。例如,以下过程:

class A
end

describe A do
  it "is instantiated by RSpec" do
    expect(subject).to be_an(A)
  end
end

您可以使用subject

自行定义主题
describe "anonymous subject" do
  subject { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

您可以在定义时为主题命名:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(a).to be_an(A)
  end
end

即使您命名主题,您仍然可以匿名参考:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

您可以定义多个命名主题。最近定义的命名主题是匿名subject

然而,主题已定义,

  1. 懒洋洋地实例化了。也就是说,在示例中引用subject或命名主题之前,所描述的类的隐式实例化或传递给subject的块的执行不会发生。如果您希望您的explict主题被急切地实例化(在其组中的示例运行之前),请说subject!而不是subject

  2. 可以隐式设置期望值(无需编写subject或命名主题的名称):

    describe A do
      it { is_expected.to be_an(A) }
    end
    

    该主题的存在是为了支持这种单行语法。

  3. 何时使用

    隐式subject(从示例组推断)很难理解,因为

    • 在幕后实例化。
    • 是否隐式使用(通过在没有显式接收器的情况下调用is_expected)或显式使用(如subject),它会向读者提供有关对象的角色或性质的信息。期待被称之为。
    • 单行示例语法没有示例描述(正常示例语法中的it的字符串参数),因此读者对示例目的的唯一信息是期待本身。

    因此,只有当所有读者都能很好地理解上下文并且实际上不需要示例描述时,它才有助于使用隐式主题。规范案例是使用shoulda匹配器测试ActiveRecord验证:

    describe Article do
      it { is_expected.to validate_presence_of(:title) }
    end
    

    一个明确的匿名subject(用subject定义,没有名字)稍微好一点,因为读者可以看到它是如何实例化的,但是

    • 它仍然可以将主题的实例化远离它所使用的位置(例如,在一个示例组的顶部,有许多使用它的示例),这仍然很难理解,并且
    • 它有隐含主题所做的其他问题。

    命名主题提供了一个意图揭示名称,但使用命名主语而不是let变量的唯一原因是,如果您想在某些时候使用匿名主题,我们只是解释了原因匿名主题很难理解。

    因此,显式匿名subject或命名主题的合法使用非常罕见

    let变量

    他们的工作方式

    let变量就像命名主题一样,除了两个不同之处:

    • 他们使用let / let!代替subject / subject!
    • 进行了定义
    • 他们没有设置匿名subject或允许隐含地调用期望。

    何时使用

    使用let减少示例之间的重复是完全合法的。但是,只有在不牺牲测试清晰度时才这样做。使用let的最安全时间是let变量的目的从名称中完全清楚(这样读者不必找到定义,可能需要很多行来理解每个例子),并且在每个例子中都以相同的方式使用它。如果其中任何一个都不正确,可以考虑在普通的旧局部变量中定义对象,或者在示例中调用工厂方法。

    let!存在风险,因为它并不是懒惰。如果有人在包含let!的示例组中添加了示例,但示例并未#&# 39; t需要let!变量,

    • 这个例子很难理解,因为读者会看到let!变量并想知道它是否以及如何影响这个例子
    • 由于创建let!变量
    • 所花费的时间,示例将比它需要的速度慢

    因此,如果有的话,只使用let!小的简单示例组中,未来的示例编写者不太可能会陷入该陷阱。

    单例期望 - 每个例子的恋物癖

    共同过度使用主题或let变量值得单独讨论。有些人喜欢这样使用它们:

    describe 'Calculator' do
      describe '#calculate' do
        subject { Calculator.calculate }
        it { is_expected.to be >= 0 }
        it { is_expected.to be <= 9 }
      end
    end
    

    (这是一个方法的简单示例,它返回一个我们需要两个期望的数字,但是如果方法返回一个需要很多期望和/或有很多期望的更复杂的值,这个样式可以有更多的例子/期望所有需要预期的副作用。)

    人们之所以这样做是因为他们已经听说每个例子应该只有一个期望(这与有效规则相混淆,即每个例子只能测试一个方法调用)或者因为他们在喜欢RSpec特技。不要使用匿名或命名主题或let变量,不要这样做!这种风格有几个问题:

    • 匿名主题不是示例的主题 - 方法是主题。以这种方式编写测试会搞砸语言,使其更难以思考。
    • 与往常一行的例子一样,没有任何空间可以解释期望的含义。
    • 必须为每个示例构建主题,这很慢。

    相反,写一个例子:

    describe 'Calculator' do
      describe '#calculate' do
        it "returns a single-digit number" do
          result = Calculator.calculate
          expect(result).to be >= 0
          expect(result).to be <= 9
        end
      end
    end
    

答案 1 :(得分:3)

Subjectlet只是帮助您整理和加快测试速度的工具。 rspec社区中的人确实使用它们,所以我不担心是否可以使用它们。它们可以类似地使用,但用途略有不同

Subject允许您声明测试主题,然后在之后的任何数量的后续测试用例中重复使用它。这减少了代码重复(干扰代码)

Letbefore: each块的替代方法,它将测试数据分配给实例变量。 Let为您提供了一些优势。首先,它缓存值而不将其赋值给实例变量。其次,它被懒惰地评估,这意味着它在规范要求之前不会被评估。因此let可以帮助您加快测试速度。我还认为let更容易阅读

答案 2 :(得分:1)

subject正在测试中,通常是实例或类。 let用于在测试中分配变量,这些变量是使用实例变量进行延迟评估的。这个帖子中有一些不错的例子。

https://github.com/reachlocal/rspec-style-guide/issues/6