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

推荐答案

总结:RSpec的主题是一个特殊变量,指的是被测试的对象.期望值可以隐式设置,这支持一行示例.在一些惯用的情况下,读者很清楚这一点,但在其他方面很难理解,应该避免.RSpec的let个变量只是延迟实例化(记忆化)的变量.它们不像主题那么难理解,但仍然会导致复杂的测试,因此应该谨慎使用.

The subject

工作原理

受试者就是被测试的对象.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或在示例中引用指定的主题时才会发生.如果你想让你的解释主题被Eager 地实例化(在其组中的一个例子运行之前),可以说是subject!而不是subject.

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

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

    主题的存在是为了支持这一行语法.

什么时候使用它

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

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

因此,it's only helpful to use an implicit subject when the context is likely to be well understood by all readers and there is really no need for an example description.规范 case 是使用shoulda matchers测试ActiveRecord验证:

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

explict anonymous subject(定义为没有名字的subject)更好一些,因为读者可以看到它是如何实例化的,但是

  • 它仍然可以将主题的实例化放在远离使用它的地方(例如,在有许多使用它的示例的示例组的顶部),这仍然很难遵循,并且
  • 它还有隐式主语所存在的其他问题.

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

所以,legitimate uses of an explicit anonymous 100 or a named subject are very rare.

let variables

它们是如何工作的

let个变量与命名主题相似,只是有两个不同:

  • 它们的定义是let/let!,而不是subject/subject!
  • 他们不设定匿名subject,也不允许隐式调用期望值.

什么时候使用它们

It's completely legitimate to use 100 to reduce duplication among examples. However, do so only when it doesn't sacrifice test clarity.使用let最安全的时间是当let变量的用途从其名称中完全清楚时(这样读者就不必找到定义,可能有很多行之遥,才能理解每个示例),并且在每个示例中都以相同的方式使用它.如果其中任何一个都不是真的,请考虑在一个简单的老局部变量中定义对象,或者在该示例中调用一个工厂方法.

100 is risky, because it's not lazy.如果有人向包含let!的示例组添加了一个示例,但该示例不需要let!变量,

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

所以,如果有的话,只在小的、简单的示例组中使用let!,这样future 的示例作者就不太可能落入这个trap .

The single-expectation-per-example fetish

有一个共同的过度使用的主题或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变量!这种风格有几个问题:

  • 匿名主题不是示例的主题——method是主题.用这种方式写测试会把语言搞砸,让人更难思考.
  • 与以往的单行示例一样,没有任何空间来解释预期的含义.
  • 每个例子都需要构建主题,这很慢.

相反,写一个例子:

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

Ruby相关问答推荐

使用 Ruby,我如何访问和比较这些嵌套的数组值?

生成带有小写字母和数字的唯一随机字符串

将数组转换为哈希,其中键是索引

Faraday中的timeout和open timeout是什么?

Rspec: expectvsexpect什么区别?

如何使用 RSpec 忽略或跳过测试方法?

rbenv 没有显示可用的 ruby​​ 版本

Scala 的扩展性是否优于其他 JVM 语言?

判断字符串是否包含Ruby数组中的任何子字符串

在文件中搜索字符串的最佳方法是什么?

hash['key'] 到 Ruby 中的 hash.key

在本地文件夹中安装 gem

在 Rails 中,如何向 String 类添加新方法?

include_examples和it_behaves_like有什么区别?

ActiveSupport::Memoizable 指的是哪种 Ruby memoize 模式?

运行 Ruby 命令时,PATH 中不安全的世界可写目录 /Users/username,模式 040777

如何在 jekyll markdown 博客中包含视频

从Ruby中的子类方法调用父类中的方法

将整个文本文件作为单个字符串读取的合理方法是什么?

在 Jekyll 中使用 Live Reload