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