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

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

问题描述:

http://betterspecs.org/#subject 有一些关于 subject的信息> 和 let.但是,我仍然不清楚它们之间的区别.此外,SO 帖子 What反对在 RSpec 测试中使用 before、let 和 subject 的论点是什么? 说最好不要使用 subjectlet.我要去哪里?我好糊涂.

http://betterspecs.org/#subject has some info about subject and let. However, I am still unclear on the difference between them. Furthermore, the SO post What is the argument against using before, let and subject in RSpec tests? said it is better to not use either subject or let. Where shall I go? I am so confused.

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

Summary: RSpec's subject is a special variable that refers to the object being tested. Expectations can be set on it implicitly, which supports one-line examples. It is clear to the reader in some idiomatic cases, but is otherwise hard to understand and should be avoided. RSpec's let variables are just lazily instantiated (memoized) variables. They aren't as hard to follow as the subject, but can still lead to tangled tests so should be used with discretion.

主题是被测试的对象.RSpec 对这个主题有一个明确的想法.它可能会或可能不会被定义.如果是,RSpec 可以在不显式引用它的情况下调用它的方法.

The subject is the object being tested. RSpec has an explicit idea of the subject. It may or may not be defined. If it is, RSpec can call methods on it without referring to it explicitly.

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

By default, if the first argument to an outermost example group (describe or context block) is a class, RSpec creates an instance of that class and assigns it to the subject. For example, the following passes:

class A
end

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

您可以使用 subject 自己定义主题:

You can define the subject yourself with subject:

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

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

You can give the subject a name when you define it:

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

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

Even if you name the subject, you can still refer to it anonymously:

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

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

You can define more than one named subject. The most recently defined named subject is the anonymous subject.

无论主题如何定义,

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

  1. It's instantiated lazily. That is, the implicit instantiation of the described class or the execution of the block passed to subject doesn't happen until subject or the named subject is referred to in an example. If you want your explict subject to be instantiated eagerly (before an example in its group runs), say subject! instead of subject.

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

Expectations can be set on it implicitly (without writing subject or the name of a named subject):

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

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

The subject exists to support this one-line syntax.

何时使用

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

When to use it

An implicit subject (inferred from the example group) is hard to understand because

  • 它在幕后实例化.
  • 无论是隐式使用(通过在没有显式接收器的情况下调用 is_expected)还是显式使用(作为 subject),它都不会向读者提供有关角色或性质的信息对其调用期望的对象.
  • 单行示例语法没有示例描述(普通示例语法中 it 的字符串参数),因此读者对示例目的的唯一信息是期望本身.
  • It's instantiated behind the scenes.
  • Whether it's used implicitly (by calling is_expected without an explicit receiver) or explicitly (as subject), it gives the reader no information about the role or nature of the object on which the expectation is being called.
  • The one-liner example syntax doesn't have an example description (the string argument to it in the normal example syntax), so the only information the reader has about the purpose of the example is the expectation itself.

因此,只有当上下文很可能被所有读者很好理解并且确实不需要示例描述时,才使用隐式主题有帮助.典型案例是使用 shoulda 匹配器测试 ActiveRecord 验证:

Therefore, 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. The canonical case is testing ActiveRecord validations with shoulda matchers:

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

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

An explict anonymous subject (defined with subject without a name) is a little better, because the reader can see how it's instantiated, but

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

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

A named subject provides an intention-revealing name, but the only reason to use a named subject instead of a let variable is if you want to use the anonymous subject some of the time, and we just explained why the anonymous subject is hard to understand.

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

So, legitimate uses of an explicit anonymous subject or a named subject are very rare.

let 变量就像命名主体,除了两个不同:

let variables are just like named subjects except for two differences:

  • 它们被定义为 let/let! 而不是 subject/subject!
  • 他们不会设置匿名 subject 或允许对其隐式调用期望.
  • they're defined with let/let! instead of subject/subject!
  • they do not set the anonymous subject or allow expectations to be called on it implicitly.

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

It's completely legitimate to use let to reduce duplication among examples. However, do so only when it doesn't sacrifice test clarity. The safest time to use let is when the let variable's purpose is completely clear from its name (so that the reader doesn't have to find the definition, which could be many lines away, to understand each example) and it is used in the same way in every example. If either of those things isn't true, consider defining the object in a plain old local variable or calling a factory method right in the example.

let! 是有风险的,因为它不是懒惰的. 如果有人将一个示例添加到包含 let! 的示例组,但是该示例不需要 let! 变量,

let! is risky, because it's not lazy. If someone adds an example to the example group that contains the let!, but the example doesn't need the let! variable,

  • 那个例子很难理解,因为读者会看到 let! 变量并想知道它是否以及如何影响这个例子
  • 这个例子会比它需要的慢,因为创建 let! 变量需要花费时间
  • that example will be hard to understand, because the reader will see the let! variable and wonder whether and how it affects the example
  • the example will be slower than it needs to be, because of the time taken to create the let! variablle

所以使用 let!,如果有的话,只在小的、简单的示例组中使用,未来的示例编写者不太可能陷入该陷阱.

So use let!, if at all, only in small, simple example groups where it's less likely that future example writers will fall into that trap.

主题或let 变量的常见过度使用值得单独讨论.有些人喜欢这样使用它们:

There is a common overuse of subjects or let variables that's worth discussing separately. Some people like to use them like this:

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

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

(This is a simple example of a method that returns a number for which we need two expectations, but this style can have many more examples/expectations if the method returns a more complicated value that needs many expectations and/or has many side effects that all need expectations.)

人们这样做是因为他们听说每个示例应该只有一个期望(这与每个示例只能测试一个方法调用的有效规则相混淆)或者因为他们喜欢 RSpec 的技巧.不要这样做,无论是匿名或命名主题还是 let 变量!这种风格有几个问题:

People do this because they've heard that one should have only one expectation per example (which is mixed up with the valid rule that one should only test one method call per example) or because they're in love with RSpec trickiness. Don't do it, whether with an anonymous or named subject or a let variable! This style has several problems:

  • 匿名主题不是示例的主题 - 方法 是主题.以这种方式编写测试会破坏语言,使其更难思考.
  • 与单行示例一样,没有任何空间来解释期望的含义.
  • 必须为每个示例构建主题,这很慢.
  • The anonymous subject isn't the subject of the examples — the method is the subject. Writing the test this way screws up the language, making it harder to think about.
  • As always with one-line examples, there isn't any room to explain the meaning of the expectations.
  • The subject has to be constructed for each example, which is slow.

相反,写一个例子:

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