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

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

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


当前回答

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

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

也许只有我这样?

其他回答

在我的rspec测试中,我已经完全用let()代替了所有实例变量的使用。我已经为一个朋友写了一个简单的例子,他用它来教一个小的Rspec课程:http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html

正如这里的一些其他答案所说,let()是延迟计算的,因此它只加载需要加载的对象。它dry了规范,使其更具可读性。事实上,我已经将Rspec let()代码移植到我的控制器中,以inherited_resource gem的风格使用。http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html

除了惰性求值外,另一个优点是,结合ActiveSupport::Concern和load-everything-in规范/support/行为,您可以创建自己的特定于应用程序的规范mini-DSL。我已经编写了针对Rack和RESTful资源的测试。

我使用的策略是工厂-一切(通过机械师+伪造/Faker)。但是,可以将它与before(:each)块结合使用,为整个示例组预加载工厂,从而使规范运行得更快:http://makandra.com/notes/770-taking-advantage-of-rspec-s-let-in-before-blocks

使用实例变量和let()之间的区别在于let()是延迟求值的。这意味着let()直到它定义的方法第一次运行时才会被求值。

before和let之间的区别在于,let()提供了一种以“级联”风格定义一组变量的好方法。这样做可以简化代码,使规范看起来更好一些。

我使用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

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

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

我总是更喜欢使用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