我倾向于使用before块来设置实例变量。然后在示例中使用这些变量。我最近偶然发现let()。根据RSpec文档,它被用来

... 定义一个记忆helper方法。该值将在同一个示例中的多个调用之间缓存,但不会在多个示例之间缓存。

这与在块之前使用实例变量有什么不同?以及什么时候应该使用let() vs before()?


当前回答

我总是更喜欢使用let而不是实例变量,原因如下:

Instance variables spring into existence when referenced. This means that if you fat finger the spelling of the instance variable, a new one will be created and initialized to nil, which can lead to subtle bugs and false positives. Since let creates a method, you'll get a NameError when you misspell it, which I find preferable. It makes it easier to refactor specs, too. A before(:each) hook will run before each example, even if the example doesn't use any of the instance variables defined in the hook. This isn't usually a big deal, but if the setup of the instance variable takes a long time, then you're wasting cycles. For the method defined by let, the initialization code only runs if the example calls it. You can refactor from a local variable in an example directly into a let without changing the referencing syntax in the example. If you refactor to an instance variable, you have to change how you reference the object in the example (e.g. add an @). This is a bit subjective, but as Mike Lewis pointed out, I think it makes the spec easier to read. I like the organization of defining all my dependent objects with let and keeping my it block nice and short.

相关链接可以在这里找到:http://www.betterspecs.org/#let

其他回答

一般来说,let()是一种更好的语法,它节省了您在所有地方输入@name符号的时间。但是,买者自负!我发现let()也会引入一些微妙的错误(或者至少让人头疼),因为在你尝试使用它之前,这个变量并不真正存在……提示符号:如果在let()之后添加一个put以查看变量是否正确,则允许规范通过,但如果没有put,则规范失败——您已经发现了这个微妙之处。

我还发现let()似乎在所有情况下都不缓存!我把它写在我的博客上:http://technicaldebt.com/?p=1242

也许只有我这样?

重要的是要记住,let是惰性求值的,不要在其中放置副作用方法,否则您将无法轻松地从let更改到(:each)之前。 你可以用let!而不是let,以便在每个场景之前对其进行评估。

let是功能性的,因为它本质上是一个过程。

我马上就发现了一个陷阱……在正在评估变更的Spec块中。

let(:object) {FactoryGirl.create :object}

expect {
  post :destroy, id: review.id
}.to change(Object, :count).by(-1)

您需要确保在期望块之外调用let。也就是说,你打电话给工厂女孩。在let块中创建。我通常通过验证对象是持久化的来做到这一点。

object.persisted?.should eq true

否则,当第一次调用let块时,由于延迟实例化,数据库将实际发生更改。

更新

只是加个注释。小心玩代码高尔夫球,或者在这种情况下,用这个答案的rspec高尔夫球。

在这种情况下,我只需要调用对象响应的某个方法。所以我调用_。persist ?_方法作为对象的真值。我要做的就是实例化对象。你可以称之为空?还是零?了。重点不在于测试本身,而在于通过调用来赋予对象生命。

所以你不能重构

object.persisted?.should eq true

object.should be_persisted 

由于对象还没有被实例化…它的懒惰。:)

更新2

利用让!即时对象创建的语法,应该可以完全避免这个问题。注意,虽然它会挫败很多懒惰的目的,非撞让。

此外,在某些情况下,你可能实际上想要利用主题语法而不是let,因为它可能会给你额外的选项。

subject(:object) {FactoryGirl.create :object}

我使用let来测试我的API规范中的HTTP 404响应,使用上下文。

为了创建资源,我使用let!但是为了存储资源标识符,我使用let。看看它是什么样子的:

let!(:country)   { create(:country) }
let(:country_id) { country.id }
before           { get "api/countries/#{country_id}" }

it 'responds with HTTP 200' { should respond_with(200) }

context 'when the country does not exist' do
  let(:country_id) { -1 }
  it 'responds with HTTP 404' { should respond_with(404) }
end

这样可以保持规范的干净和可读性。

反对的声音在这里:经过5年的rspec,我不太喜欢让。

1. 惰性计算常常使测试设置令人困惑

当在setup中声明的一些东西实际上不会影响状态,而其他东西则会影响状态时,很难对setup进行推理。

最终,出于无奈,有人才把let改成了let!(没有懒惰的评估也是一样的),以便让他们的规范工作。如果这对他们来说是可行的,一个新的习惯就会诞生:当一个新的规范被添加到一个旧的套件中,但它不起作用时,作者尝试的第一件事就是在随机let调用中添加bangs。

很快,所有的性能优势都消失了。

2. 对于非rspec用户来说,特殊语法是不常见的

我宁愿把Ruby教给我的团队,而不是rspec的技巧。实例变量或方法调用在这个项目和其他项目中都很有用,让语法只在rspec中有用。

3.这些“好处”让我们很容易忽略好的设计变更

Let()适用于不希望反复创建的昂贵依赖项。 它还可以很好地与subject配合使用,允许您停止重复调用多参数方法

重复多次的昂贵依赖项和具有大签名的方法都是我们可以改进代码的地方:

也许我可以引入一种新的抽象,将依赖项与我的其余代码隔离开来(这将意味着更少的测试需要它)。 可能被测试的代码做的太多了 也许我需要注入更智能的对象,而不是一长串原语 也许我违反了“告诉-不要问”的规定 也许昂贵的代码可以做得更快(很少-注意这里的过早优化)

在所有这些情况下,我可以使用rspec魔法来缓解困难测试的症状,也可以尝试解决原因。我觉得过去几年我在前者上花费了太多时间,现在我想要一些更好的代码。

回答最初的问题:我宁愿不使用,但我仍然使用let。我主要使用它来适应团队其他成员的风格(似乎世界上大多数Rails程序员现在都深入到他们的rspec魔法中,所以这是经常发生的)。有时,当我在一些我无法控制的代码中添加测试,或者没有时间重构到更好的抽象时,我就会使用它:即当唯一的选择是止痛药时。