人们似乎已经解释了它们不同的一些基本方式,但忽略了before(:all),并且没有准确解释为什么应该使用它们。
我认为实例变量在绝大多数规范中都没有使用,部分原因是this answer 中提到的原因,所以我不会在这里提及它们。
让块
let 块中的代码仅在引用时执行,延迟加载意味着这些块的顺序无关紧要。这为您提供了强大的功能,可以通过您的规范减少重复设置。
一个(非常小的和做作的)例子是:
let(:person) { build(:person) }
subject(:result) { Library.calculate_awesome(person, has_moustache) }
context 'with a moustache' do
let(:has_moustache) { true }
its(:awesome?) { should be_true }
end
context 'without a moustache' do
let(:has_moustache) { false }
its(:awesome?) { should be_false }
end
您可以看到has_moustache 在每种情况下的定义都不同,但无需重复subject 的定义。需要注意的重要一点是,将使用当前上下文中定义的最后一个 let 块。这对于设置用于大多数规范的默认值很有用,如果需要,可以将其覆盖。
例如,检查 calculate_awesome 的返回值是否传递了 person 模型并将 top_hat 设置为 true,但没有小胡子:
context 'without a moustache but with a top hat' do
let(:has_moustache) { false }
let(:person) { build(:person, top_hat: true) }
its(:awesome?) { should be_true }
end
关于 let 块的另一点需要注意的是,如果您正在搜索已保存到数据库中的内容(即Library.find_awesome_people(search_criteria)),则不应使用它们,因为除非它们已经被保存到数据库中,否则它们不会被保存到数据库中。参考。 let! 或 before 块是这里应该使用的。
另外,永远不要永远使用before来触发let块的执行,这就是let!的用途!
让!块
let! 块按照它们定义的顺序执行(很像之前的块)。与 before 块的一个核心区别是您获得了对该变量的显式引用,而不是需要回退到实例变量。
与let 块一样,如果使用相同名称定义了多个let! 块,则将在执行中使用最新的块。核心区别在于 let! 块这样使用会被执行多次,而 let 块只会执行最后一次。
before(:each) 块
before(:each) 是块前的默认值,因此可以引用为before {},而不是每次都指定完整的before(:each) {}。
在一些核心情况下使用before 块是我个人的偏好。如果出现以下情况,我将使用 before 块:
- 我正在使用模拟、存根或双打
- 有任何合理大小的设置(通常这表明您的工厂特性设置不正确)
- 有许多变量我不需要直接引用,但在设置时是必需的
- 我在 Rails 中编写功能控制器测试,我想执行一个特定的测试请求(即
before { get :index })。尽管在很多情况下您可以为此使用 subject,但如果您不需要参考,有时会感觉更明确。
如果您发现自己正在为您的规范编写大型 before 块,请检查您的工厂并确保您完全了解特征及其灵活性。
before(:all) 块
这些只会在当前上下文(及其子项)中的规范之前执行一次。如果编写正确,这些可以发挥很大的优势,因为在某些情况下,这可以减少执行和工作量。
一个例子(几乎不会影响执行时间)是模拟一个 ENV 变量进行测试,你应该只需要做一次。
希望有帮助:)