RSpec:let和before块有什么区别?


90

letbeforeRSpec中的块有什么区别?

以及何时使用它们?

在下面的示例中,什么是更好的方法(之前或之后)?

let(:user) { User.make !}
let(:account) {user.account.make!}

before(:each) do
 @user = User.make!
 @account = @user.account.make!
end

我研究了这个stackoverflow帖子

但是像上面这样定义允许关联的东西好吗?


1
基本上,“ let”由不喜欢实例变量的人使用。作为附带说明,您应该考虑使用FactoryGirl或类似工具。
2011年

Answers:


146

人们似乎已经解释了它们之间区别的一些基本方法,但被遗漏了,before(:all)并且没有确切解释为什么应该使用它们。

我认为实例变量在绝大多数规范中都没有使用,部分原因是此答案中提到的原因,因此在这里我不会将它们作为选项。

让块

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_awesomeif 的返回值是否传递了设置为true 的person模型top_hat,但没有胡须是:

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块)。与before块的一个核心区别是,您可以对此变量进行显式引用,而无需使用实例变量。

let块一样,如果let!使用相同的名称定义了多个块,则最新的将用于执行。核心区别在于,let!如果这样使用,则块将被执行多次,而该let块将仅在最后一次执行。

before(:each)块

before(:each)是默认的before块,因此可以被引用为before {}而不是before(:each) {}每次都指定完整的。

before在一些核心情况下使用块是我个人的喜好。在以下情况下,我将使用before块:

  • 我正在使用嘲笑,存根或双打
  • 有任何合理大小的设置(通常这表明您的工厂特性未正确设置)
  • 我不需要直接引用许多变量,但是设置这些变量是必需的
  • 我正在用Rails编写功能控制器测试,我想执行一个特定的测试请求(即before { get :index })。即使您可以subject在许多情况下使用此功能,但如果您不需要参考,有时感觉也会更加明确。

如果发现自己before为规格写了大块代码,请检查您的工厂,并确保您完全了解特质及其灵活性。

before(:all)块

在当前上下文中的规范(及其子规范)之前,它们仅执行一次。如果编写得当,这些可以发挥很大的优势,因为在某些情况下,这样做可以减少执行和工作量。

一个示例(根本不会影响执行时间)是模拟一个ENV变量进行测试,您只需执行一次即可。

希望有帮助:)


对于我是Minitest用户,但我没有注意到此问答的RSpec标签,该before(:all)选项在Minitest中不存在。这是评论中的一些解决方法:github.com/seattlerb/minitest/issues/61
MrYoshiji 2016年

引用的文章是一条无效链接:\
迪伦·皮尔斯

谢谢@DylanPierce。我找不到该文章的任何副本,因此我引用了一个SO答案代替它:)
Jay

谢谢:)非常感谢
Dylan Pierce

1
its不再位于rspec-core中。一种更现代,更惯用的方式是it { is_expected.to be_awesome }
Zubin,

26

我几乎总是喜欢let。您链接的帖子指定的let速度也更快。但是,有时候,当必须执行许多命令时,我可以使用before(:each)它,因为当涉及到许多命令时,其语法更加清晰。

在您的示例中,我绝对希望使用let而不是before(:each)。一般来说,当仅完成一些变量初始化时,我倾向于使用let


15

未提及的最大区别是,使用定义的变量let在您首次调用之前不会实例化。因此,尽管一个before(:each)块可以实例化所有变量,但let让我们定义可以在多个测试中使用的多个变量,但它不会自动实例化它们。如果您不希望知道这些,那么如果您希望所有数据都预先加载,那么您的测试可能会再次被您咬伤。在某些情况下,您甚至可能想要定义多个let变量,然后使用一个before(:each)块来调用每个let实例,以确保可以从其开始使用数据。


20
您可以let!用来定义在每个示例之前调用的方法。参见RSpec文档
Jim Stewart

3

看来您使用的是Machinist。当心,您可能会发现make的一些问题!let的内部(非爆炸版本)发生在全局夹具事务之外(如果您也使用事务性夹具),因此会破坏其他测试的数据。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.