我倾向于使用before块来设置实例变量。然后,在示例中使用这些变量。我最近来了let()
。根据RSpec文档,它用于
...定义记忆的辅助方法。该值将在同一示例中的多个调用之间缓存,但不会跨示例。
这与在before块中使用实例变量有何不同?还有什么时候应该使用let()
vs before()
?
我倾向于使用before块来设置实例变量。然后,在示例中使用这些变量。我最近来了let()
。根据RSpec文档,它用于
...定义记忆的辅助方法。该值将在同一示例中的多个调用之间缓存,但不会跨示例。
这与在before块中使用实例变量有何不同?还有什么时候应该使用let()
vs before()
?
Answers:
let
由于以下两个原因,我总是更喜欢实例变量:
nil
,这可能导致细微的错误和误报。由于let
创建了一种方法,因此您NameError
在拼写错误时会得到a ,我认为这是可取的。它也使重构规格变得更加容易。before(:each)
钩子将每个例子之前运行,即使例如不使用任何在钩定义的实例变量。通常这没什么大不了的,但是如果实例变量的设置花费很长时间,那么您就在浪费周期。对于由定义的方法let
,只有在示例调用它时,初始化代码才会运行。@
)。let
并使我的代码it
块美观又简短。相关链接可以在这里找到:http : //www.betterspecs.org/#let
let
来定义所有依赖对象以及before(:each)
设置所需的配置或示例所需的任何模拟/存根会很有帮助。与包含所有这些的钩子之前相比,我更喜欢这种方法。而且,let(:foo) { Foo.new }
它的噪音要小一些(要点多)before(:each) { @foo = Foo.new }
。这是我的用法示例:github.com/myronmarston/vcr/blob/v1.7.0/spec/vcr/util / ...
NoMethodError
警告而不是警告,但YMMV。
foo = Foo.new(...)
,然后foo
在以后的行中使用用户。稍后,您在相同的示例组中编写了一个新示例,该示例也需要以Foo
相同的方式实例化。此时,您需要重构以消除重复。您可以foo = Foo.new(...)
从示例中删除这些行,并以不替换let(:foo) { Foo.new(...) }
示例使用方式的方式替换foo
。但是,如果要重构,则before { @foo = Foo.new(...) }
还必须将示例中的引用更新foo
为@foo
。
使用实例变量和之间的差异let()
在于,let()
是懒评估。这意味着let()
直到第一次运行它定义的方法时,才对其进行评估。
之间的区别before
和let
是let()
给你定义的“级联”的风格一组变量的一个很好的方式。这样,通过简化代码,规范看起来会更好一些。
let
如果您需要每次进行评估,就不应该使用?例如,在父模型上触发某些行为之前,我需要一个子模型出现在数据库中。我不必在测试中引用该子模型,因为我正在测试父模型的行为。目前,我正在使用该let!
方法,但是将其设置为可能会更加明确before(:each)
。
我已经在rspec测试中完全替换了实例变量的所有用法,以使用let()。我为一个用它来教Rspec小型课程的朋友写了一个快捷示例:http ://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html
就像这里的其他答案所说的那样,let()是延迟计算的,因此它只会加载需要加载的内容。它使规范变干并使其更具可读性。实际上,我已经继承了Rspec let()代码,以便以Inherited_resource gem的样式在控制器中使用。http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html
与惰性评估一起,另一个优点是,与ActiveSupport :: Concern结合使用,以及负载不一的规范/支持/行为,您可以创建自己的针对特定应用的规范mini-DSL。我已经写了一些针对Rack和RESTful资源的测试。
我使用的策略是“工厂一切”(通过机械师+伪造/ Faker)。但是,可以将它与before(:each)块结合使用来为整个示例组集预加载工厂,从而使规范运行得更快:http : //makandra.com/notes/770-taking-advantage RSpec放在块之前
# spec/friendship_spec.rb
和# spec/comment_spec.rb
示例,您是否认为它们使可读性降低?我不知道从何users
而来,需要深入研究。
let()
过去几天我一直在使用,但我个人没有什么区别,除了Myron提到的第一个优势。而且我不太确定要放手,什么也不放手,也许是因为我很懒,而且我喜欢预先查看代码而不必打开另一个文件。感谢您的意见。
通常,这let()
是一种更好的语法,它可以节省您@name
在各处输入符号的麻烦。但是,告诫者!我发现let()
还引入了微妙的错误(或者至少挠头),因为变量实际上并不存在,直到您尝试使用它......告诉故事标志:如果将一个puts
后let()
看到的变量是正确允许规范通过,但没有puts
规格失败-您已经发现了这种微妙之处。
我还发现let()
似乎并非在所有情况下都可以缓存!我在博客中写了它:http : //technicaldebt.com/?p=1242
也许就是我?
let
始终在单个示例的持续时间内记住该值。它不会在多个示例中记住该值。before(:all)
相反,允许您在多个示例中重复使用初始化的变量。
let!
就是设计目的。 relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/…–
let本质上是一个Proc。还对其进行了缓存。
我马上就发现了一个陷阱……在评估变更的Spec块中。
let(:object) {FactoryGirl.create :object}
expect {
post :destroy, id: review.id
}.to change(Object, :count).by(-1)
您需要确保let
在您的Expect块之外调用。也就是说,您正在调用FactoryGirl.create
let块。我通常通过验证对象是否持久来做到这一点。
object.persisted?.should eq true
否则,let
由于延迟实例化,第一次调用该块时,实际上将发生数据库更改。
更新资料
只是添加一个注释。请谨慎打代码高尔夫,或者在这种情况下使用此答案进行rspec高尔夫。
在这种情况下,我只需要调用对象响应的某些方法即可。因此,我_.persisted?
在对象上调用_方法作为其真实性。我要做的就是实例化该对象。你可以叫空吗?还是零?太。关键不是测试,而是通过调用对象来延长对象寿命。
所以你无法重构
object.persisted?.should eq true
成为
object.should be_persisted
因为该对象尚未实例化...它是惰性的。:)
更新2
利用放手!即时对象创建的语法,应该完全避免此问题。请注意,尽管这样做会打败非猛烈放任的懒惰的许多目的。
同样,在某些情况下,您实际上可能希望利用主题语法而不是让它使用,因为它可能会给您带来更多选择。
subject(:object) {FactoryGirl.create :object}
约瑟夫(Joseph)注意-如果您正在创建数据库对象,before(:all)
则不会在事务中捕获它们,并且您很可能在测试数据库中遗忘。采用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
默认情况下,“之前”暗含before(:each)
。参见Rspec图书,版权2010,第228页。
before(scope = :each, options={}, &block)
我曾经before(:each)
为每个示例组添加一些数据,而不必调用let
在“ it”块中创建数据的方法。在这种情况下,“ it”块中的代码更少。
let
如果在某些示例中需要一些数据,而在其他示例中则不需要,我会使用。
之前和让它们都非常适合干燥“ it”块。
为避免混淆,“ let”与相同before(:all)
。“让”为每个示例(“ it”)重新评估其方法和值,但在同一示例中跨多个调用缓存该值。您可以在此处了解更多信息:https : //www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let
在这里有不同的声音:经过5年的rspec我不太喜欢let
。
当在安装程序中声明的某些事物实际上并未影响状态,而另一些事物确实在影响状态时,就很难对安装程序进行推理了。
最终,出于无奈有人只是改变let
到let!
(不懒评价同一件事),以获得他们的规范工作。如果这对他们有用,那么就会养成新的习惯:当将新规范添加到较旧的套件中而行不通时,作者尝试的第一件事就是在随机中添加刘海let
调用中。
很快所有的性能优势都消失了。
我更愿意向我的团队教Ruby,而不是rspec的技巧。实例变量或方法调用在该项目以及其他项目中都非常有用,let
语法仅在rspec中有用。
let()
对于我们不想一遍又一遍创建的昂贵依赖项很有用。它也与subject
,使您可以避免重复调用多参数方法
昂贵的依赖关系多次重复出现,并且带有大签名的方法都是我们可以使代码变得更好的两个方面:
在所有这些情况下,我都可以使用舒缓的rspec magic香脂解决困难测试的症状,或者尝试解决原因。我觉得过去几年我花了太多时间在前者上,现在我想要一些更好的代码。
要回答原始问题:我不愿意,但是我仍然使用let
。我主要使用它来适应团队其他成员的风格(似乎世界上大多数Rails程序员现在都已经深入到他们的rspec魔术中了,这种情况经常出现)。有时,当我在一些我无法控制的代码中添加测试或没有时间重构更好的抽象时使用它:即,唯一的选择是止痛药时。
我用 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
这样可以使规范保持清洁和可读性。