在 Rspec 中,让我们使用延迟实例化,这样let(:foo) { create(...) }
在有东西调用它之前不会初始化它。通常这很好,因为它只在需要时使用,并使 rspec 测试时间更快。
但是,有时您会有一个需要该变量但没有显式调用它的规范。因此,使用延迟实例化,规范将失败。
解决方案是轰轰烈烈!let!(:foo) { create(...) }
将强制初始化变量。
一些开发人员似乎非常反对这一点,并且更喜欢:
let(:foo) { create(...) }
before do
foo
end
以强制初始化。
这有什么原因吗?这两种方法之间有什么区别吗?
我可以想到一个区别:before
块会复合,你可以用let
覆盖let!
,反之亦然。我给你举个例子:
context do
let!(:foo) { create(:foo) }
let(:bar) { create(:bar) }
before { bar }
context do
# if in this context you wish to switch back to "lazy" versions you can
# do that for :foo, just do:
let(:foo) { create(:foo) }
# but you can't "undo" before, even if you define an empty one:
before { }
# it does not cancel the `before` blocks defined in higher contexts
end
end
编辑:我刚刚意识到这并不能真正回答为什么有人更喜欢before
而不是let!
的问题。也许:正如评论中提到的,顺序是不同的,但如果你依赖规格中的这种细微差别 - 它已经太复杂了。
很多情况是风格问题,开发人员并不完全了解RSpec的主要功能,很多时候人们就是没有意义。人类不是机器,特别是在时间压力下,开发人员会做他们在理想条件下不会做的事情:)。
但所呈现的两种情况并不完全相同。 例如,如果您使用的是subject
,则在let!
初始化之前,它会在before
钩子中进行评估,而不是在it
内部进行评估。我没有测试,但我相信这些情况应该显示差异:
let!(:car) { create(:car) }
let(:driver) { create(:driver) }
subject { driver.car() }
it { expect(subject).to eq car } # Fail:
这强制car
之前创建并可用于subject
:
let(:driver) { create(:driver) }
subject { driver.car() }
before { create(:car) }
it { expect(subject).to eq car } # Success