http://betterspecs.org/#subject 有一些关于 subject
和 let
的信息。但是,我仍然不清楚它们之间的区别。此外,SO 帖子 What is the argument against using before, let and subject in RSpec tests? 说最好不要使用 subject
或 let
。我该去哪里?我感到很困惑。
总结:RSpec 的主题是一个特殊的变量,它指代被测试的对象。可以对其隐式设置期望,这支持单行示例。在某些惯用的情况下,读者很清楚,但在其他方面很难理解,应该避免。 RSpec 的 let
变量只是延迟实例化(记忆)的变量。它们不像主题那样难以理解,但仍可能导致复杂的测试,因此应谨慎使用。
主题
这个怎么运作
主题是被测试的对象。 RSpec 对这个主题有一个明确的想法。它可能会或可能不会被定义。如果是,RSpec 可以在不显式引用它的情况下调用它的方法。
默认情况下,如果最外层示例组(describe
或 context
块)的第一个参数是一个类,则 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
。
然而主题是定义的,
它是惰性实例化的。也就是说,所描述的类的隐式实例化或传递给主题的块的执行不会发生,直到在示例中引用主题或命名的主题。如果您希望您的显式主题被急切地实例化(在其组中的示例运行之前),请说主题!而不是主题。可以对其隐式设置期望(无需编写主题或命名主题的名称): describe A do it { is_expected.to be_an(A) } end 主题的存在是为了支持这种单行语法。
何时使用
隐含的 subject
(从示例组推断)很难理解,因为
它在幕后实例化。
无论是隐式使用(通过调用 is_expected 而不使用显式接收者)还是显式使用(作为主题),它都不会向读者提供有关调用期望的对象的角色或性质的信息。
单行示例语法没有示例描述(普通示例语法中的字符串参数),因此读者关于示例目的的唯一信息是期望本身。
因此,只有当所有读者都可能很好地理解上下文并且确实不需要示例描述时,才使用隐含的主题。典型案例是使用 shoulda 匹配器测试 ActiveRecord 验证:
describe Article do
it { is_expected.to validate_presence_of(:title) }
end
显式匿名 subject
(用不带名称的 subject
定义)要好一些,因为读者可以看到它是如何实例化的,但是
它仍然可以将主题的实例化放在远离它使用的地方(例如,在有许多使用它的示例的示例组的顶部),这仍然很难理解,并且
它还有隐式主语的其他问题。
命名主题提供了一个意图揭示的名称,但使用命名主题而不是 let
变量的唯一原因是如果您想在某些时候使用匿名主题,我们刚刚解释了为什么匿名主题很难去理解。
因此,显式匿名 subject
或命名主题的合法使用非常罕见。
让变量
他们是如何工作的
let
变量与命名主题类似,但有两点不同:
它们是用 let/let 定义的!而不是主题/主题!
他们不设置匿名主题或允许对其隐式调用期望。
何时使用它们
使用 let
来减少示例之间的重复是完全合法的。但是,只有在不牺牲测试清晰度的情况下才这样做。 使用 let
的最安全时间是当 let
变量的用途从其名称中完全清楚时(这样读者就不必找到定义,可能在很多行之外,以理解每个示例),并且在每个示例中都以相同的方式使用它。如果其中任何一个不正确,请考虑在普通的旧局部变量中定义对象或在示例中直接调用工厂方法。
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
(这是一个方法的简单示例,它返回一个我们需要两个期望的数字,但是如果该方法返回一个需要许多期望和/或具有许多副作用的更复杂的值,则这种样式可以有更多示例/期望都需要期待。)
人们这样做是因为他们听说每个示例应该只有一个期望(这与每个示例只应该测试一个方法调用的有效规则相混淆)或者因为他们爱上了 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
Subject
和 let
只是帮助您整理和加快测试的工具。 rspec 社区中的人确实使用它们,所以我不会担心是否可以使用它们。它们可以类似地使用,但用途略有不同
Subject
允许您声明一个测试主题,然后在随后的任意数量的测试用例中重复使用它。这减少了代码重复(DRYing up your code)
Let
是 before: each
块的替代品,它将测试数据分配给实例变量。 Let
为您提供了几个优势。首先,它缓存值而不将其分配给实例变量。其次,它是惰性求值的,这意味着它在规范要求之前不会被求值。因此,let
可以帮助您加快测试速度。我也认为 let
更容易阅读
subject
是正在测试的内容,通常是实例或类。 let
用于在您的测试中分配变量,这些变量是惰性评估的,而不是使用实例变量。这个线程中有一些很好的例子。
https://github.com/reachlocal/rspec-style-guide/issues/6
it "marks a task complete", :aggregate_failures do
之类的行中使用:aggregate_failures
标记(取自 Rails 5 Test Prescriptions 书)expect(result.to_s).to match(/^[0-9]$/)
会更合适 - 我知道这很丑,但它确实测试了您所说的内容,或者也许使用between
+is_a? Integer
,但在这里您也在测试类型。只是let
.. 它不应该成为关注的对象,实际上重新评估示例之间的值可能会更好。否则为帖子 +1let!
危险性的解释,而且我还没有独自说服我的队友。我要把这个答案发过去。