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

new*_*cpp 62 ruby unit-testing rspec

http://betterspecs.org/#subject有一些关于subject和的信息let.但是,我仍然不清楚它们之间的区别.此外,SO帖子反对在RSpec测试中使用之前,之后和主题的论点是什么?说最好不要使用subject或者let.我该去哪儿?我感到很困惑.

Dav*_*uth 137

简介: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
Run Code Online (Sandbox Code Playgroud)

您可以自己定义主题subject:

describe "anonymous subject" do
  subject { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end
Run Code Online (Sandbox Code Playgroud)

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

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

即使您命名主题,您仍然可以匿名引用它:

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

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

然而,主题是定义的,

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

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

    describe A do
      it { is_expected.to be_an(A) }
    end
    
    Run Code Online (Sandbox Code Playgroud)

    该主题用于支持这种单行语法.

什么时候使用它

隐含的subject(从示例组中推断出来)很难理解,因为

  • 它在幕后实例化.
  • 无论是隐式使用(通过is_expected没有显式接收器调用)还是显式(as subject),它都不会向读者提供有关调用期望的对象的角色或性质的信息.
  • 单行示例语法没有示例描述(it正常示例语法中的字符串参数),因此读者对示例目的的唯一信息是期望本身.

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

describe Article do
  it { is_expected.to validate_presence_of(:title) }
end
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

let 变量

他们如何工作

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

  • 他们用let/ let!而不是subject/ 定义subject!
  • 他们没有设置匿名subject或允许隐含地调用期望.

何时使用它们

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

let!风险很大,因为它不是懒惰的.如果有人向包含该示例的组添加示例let!,但该示例不需要该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
Run Code Online (Sandbox Code Playgroud)

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

人们之所以这样做,是因为他们听说每个例子应该只有一个期望(这与有效规则相混淆,即每个例子只测试一个方法调用),或者因为他们喜欢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
Run Code Online (Sandbox Code Playgroud)

  • 哇!+!可以这么说,"单一期望 - 每个例子的迷信"在黄金方面是值得的.我从来没有觉得有必要非常密切地遵循那个(错误的)规则,但现在我对那些试图强迫我们其他人强迫的人很有武装.谢谢! (12认同)
  • 同样,如果您希望多期望块运行所有预期行(而不是如果第一个失败则不运行),则可以在诸如``标记任务完成''的行中使用`:aggregate_failures`标记, :aggregate_failuresdo(摘自Rails 5 Test Prescriptions) (2认同)
  • 我也反对炒作和迷恋,“每个示例单一期望”主要适合那些喜欢将许多不相关的期望组合在一个示例中的人。从语义上讲,您的示例并不理想,因为它不验证单个数字,而是验证 [0, 9] 中的实数 - 令人惊讶的是,它可以用更易读的单个期望进行编码`期望(结果).to be_ Between(0, 9)`。 (2认同)

nik*_*ypx 6

subject是正在测试的内容,通常是一个实例或一个类。let用于在测试中分配变量,这些变量是延迟评估的而不是使用实例变量。这个线程中有一些很好的例子。

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


Ren*_*Ren 5

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

Subject允许您声明一个测试主题,然后将其重用于任意数量的后续测试用例。这减少了代码重复(干掉你的代码)

Letbefore: each块的替代品,块将测试数据分配给实例变量。 Let给你几个优势。首先,它缓存该值而不将其分配给实例变量。其次,它是惰性求值的,这意味着在规范要求它之前不会对它求值。从而let帮助您加快测试速度。我也认为let更容易阅读