我倾向于使用before块来设置实例变量。然后在示例中使用这些变量。我最近偶然发现let()。根据RSpec文档,它被用来
... 定义一个记忆helper方法。该值将在同一个示例中的多个调用之间缓存,但不会在多个示例之间缓存。
这与在块之前使用实例变量有什么不同?以及什么时候应该使用let() vs before()?
我倾向于使用before块来设置实例变量。然后在示例中使用这些变量。我最近偶然发现let()。根据RSpec文档,它被用来
... 定义一个记忆helper方法。该值将在同一个示例中的多个调用之间缓存,但不会在多个示例之间缓存。
这与在块之前使用实例变量有什么不同?以及什么时候应该使用let() vs before()?
当前回答
使用实例变量和let()之间的区别在于let()是延迟求值的。这意味着let()直到它定义的方法第一次运行时才会被求值。
before和let之间的区别在于,let()提供了一种以“级联”风格定义一组变量的好方法。这样做可以简化代码,使规范看起来更好一些。
其他回答
在我的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
反对的声音在这里:经过5年的rspec,我不太喜欢让。
1. 惰性计算常常使测试设置令人困惑
当在setup中声明的一些东西实际上不会影响状态,而其他东西则会影响状态时,很难对setup进行推理。
最终,出于无奈,有人才把let改成了let!(没有懒惰的评估也是一样的),以便让他们的规范工作。如果这对他们来说是可行的,一个新的习惯就会诞生:当一个新的规范被添加到一个旧的套件中,但它不起作用时,作者尝试的第一件事就是在随机let调用中添加bangs。
很快,所有的性能优势都消失了。
2. 对于非rspec用户来说,特殊语法是不常见的
我宁愿把Ruby教给我的团队,而不是rspec的技巧。实例变量或方法调用在这个项目和其他项目中都很有用,让语法只在rspec中有用。
3.这些“好处”让我们很容易忽略好的设计变更
Let()适用于不希望反复创建的昂贵依赖项。 它还可以很好地与subject配合使用,允许您停止重复调用多参数方法
重复多次的昂贵依赖项和具有大签名的方法都是我们可以改进代码的地方:
也许我可以引入一种新的抽象,将依赖项与我的其余代码隔离开来(这将意味着更少的测试需要它)。 可能被测试的代码做的太多了 也许我需要注入更智能的对象,而不是一长串原语 也许我违反了“告诉-不要问”的规定 也许昂贵的代码可以做得更快(很少-注意这里的过早优化)
在所有这些情况下,我可以使用rspec魔法来缓解困难测试的症状,也可以尝试解决原因。我觉得过去几年我在前者上花费了太多时间,现在我想要一些更好的代码。
回答最初的问题:我宁愿不使用,但我仍然使用let。我主要使用它来适应团队其他成员的风格(似乎世界上大多数Rails程序员现在都深入到他们的rspec魔法中,所以这是经常发生的)。有时,当我在一些我无法控制的代码中添加测试,或者没有时间重构到更好的抽象时,我就会使用它:即当唯一的选择是止痛药时。
使用实例变量和let()之间的区别在于let()是延迟求值的。这意味着let()直到它定义的方法第一次运行时才会被求值。
before和let之间的区别在于,let()提供了一种以“级联”风格定义一组变量的好方法。这样做可以简化代码,使规范看起来更好一些。
请Joseph注意——如果您在before(:all)中创建数据库对象,它们将不会在事务中被捕获,您更有可能在测试数据库中留下cruft。使用before(:each)代替。
使用let及其惰性求值的另一个原因是,你可以在上下文中重写let来测试一个复杂的对象,就像下面这个非常人为的例子:
context "foo" do
let(:params) do
{ :foo => foo, :bar => "bar" }
end
let(:foo) { "foo" }
it "is set to foo" do
params[:foo].should eq("foo")
end
context "when foo is bar" do
let(:foo) { "bar" }
# NOTE we didn't have to redefine params entirely!
it "is set to bar" do
params[:foo].should eq("bar")
end
end
end
重要的是要记住,let是惰性求值的,不要在其中放置副作用方法,否则您将无法轻松地从let更改到(:each)之前。 你可以用let!而不是let,以便在每个场景之前对其进行评估。