http://betterspecs.org/#subject提供了有关subject
和的信息let
。但是,我仍然不清楚它们之间的区别。此外,SO post对RSpec测试中的before,let和subject的使用有何论点?说最好不要使用subject
或let
。我应该去哪里?我感到很困惑。
http://betterspecs.org/#subject提供了有关subject
和的信息let
。但是,我仍然不清楚它们之间的区别。此外,SO post对RSpec测试中的before,let和subject的使用有何论点?说最好不要使用subject
或let
。我应该去哪里?我感到很困惑。
Answers:
简介: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
。
无论主题如何定义
懒惰地实例化。也就是说,在一个示例中引用了指定的主题subject
之前,不会发生所描述类的隐式实例化或传递给其的块的执行subject
。如果您希望热切地实例化显式主题(在其组中的示例运行之前),请使用subject!
代替subject
。
可以对其隐式设置期望(无需书写subject
或指定主题的名称):
describe A do
it { is_expected.to be_an(A) }
end
存在该主题以支持这种单行语法。
隐式subject
(从示例组推论)很难理解,因为
is_expected
不带显式接收器的调用)还是显式使用(如subject
),它都不会向读者提供有关在其上调用期望的对象的角色或性质的信息。it
普通示例语法中的string参数),因此读者唯一了解示例目的的信息就是期望本身。因此,仅当上下文可能被所有读者很好地理解并且确实不需要示例描述时,使用隐式主题才有帮助。规范的案例是使用比对匹配器测试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!
,但示例不需要该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
:aggregate_failures
在行中使用标记it "marks a task complete", :aggregate_failures do
(摘自Rails 5 Test Prescriptions)
expect(result).to be_between(0, 9)
。
expect(result.to_s).to match(/^[0-9]$/)
-我知道这很丑陋,但实际上可以测试您在说什么,或者使用between
+ is_a? Integer
,但是在这里您正在测试类型也一样。而且,let
它不应该成为关注的对象,而实际上重新评估示例之间的值可能会更好。否则,该职位+1
let!
并没有自己说服我的队友。我将发送此答案。
subject
是要测试的内容,通常是实例或类。let
用于在测试中分配变量,这些变量相对于使用实例变量而言是惰性计算的。此线程中有一些不错的示例。