什么时候使用RSpec let()?

我倾向于在块之前使用设置实例variables。 然后我在这些例子中使用这些variables。 我最近遇到let() 。 根据RSpec文档,它已经习惯了

…定义一个记忆辅助方法。 该值将在同一个示例中跨多个调用进行caching,但不会跨越示例。

这与在前面块中使用实例variables有什么不同? 还应该什么时候使用let() vs before()

我总是喜欢let一个实例variables有几个原因:

  • 实例variables在引用时popup。 这意味着如果你用手指敲实例variables的话,会创build一个新的,并初始化nil ,这会导致细微的错误和误报。 既然let创build了一个方法,当你拼错它的时候,你会得到一个NameError ,我觉得这更好。 这也使重构规格更容易。
  • 一个before(:each)钩子将在每个示例之前运行,即使该示例没有使用钩子中定义的任何实例variables。 这通常不是什么大问题,但是如果实例variables的设置需要很长时间,那么你正在浪费周期。 对于由let定义的方法,只有在示例调用初始化代码时才会运行初始化代码。
  • 您可以将示例中的局部variables直接重构为let,而无需更改示例中的引用语法。 如果你重构一个实例variables,你必须改变你在示例中引用对象的方式(例如添加@ )。
  • 这有点主观,但正如迈克·刘易斯指出的那样,我认为这使得规范更易于阅读。 我喜欢定义所有我的依赖对象的组织, let我保持我的块好和短。

使用实例variables和let()之间的区别在于let()懒惰评估的 。 这意味着let()不会被计算,直到它定义的方法第一次运行。

letlet之间的区别在于, let()为您提供了一种以“级联”风格定义一组variables的好方法。 通过这样做,通过简化代码,规范看起来好一点。

我在我的rspectesting中完全replace了实例variables的所有用法,使用let()。 我为一个朋友写了一个很快的例子,用它来教一个小的Rspec类: http ://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html

由于这里的一些其他答案说,let()是懒惰的评估,所以它只会加载那些需要加载。 它干燥规格,使其更具可读性。 实际上,我已经用inherited_resource gem移植了在我的控制器中使用的Rspec let()代码。 http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html

除了懒惰的评估,另一个优势是,结合ActiveSupport :: Concern,以及载入规范/支持/行为中的所有内容,您可以创build您自己的专用于您的应用程序的专用mini-DSL。 我已经写了一个testing机架和RESTful资源。

我使用的策略是Factory-everything(通过Machinist + Forgery / Faker)。 但是,可以将它与before(:each)块组合使用,以便为整个示例组组预加载工厂,从而使规格运行速度更快: http : //makandra.com/notes/770-taking-advantage -of-rspec的-S-LET-在前方的块

记住let是懒惰评估的,而不是把副作用方法放在里面是非常重要的,否则你将不能轻易地从let改变到before(:each) 。 你可以用let! 而不是让它在每个场景之前进行评估。

一般来说, let()是一个更好的语法,它可以节省您在所有地方input@name符号。 但是,要警惕! 我发现let()还引入了细微的错误(至less是头部划伤),因为variables在你尝试使用它之前并不存在。告诉故事符号:如果在let()该variables是正确的允许一个规范通过,但没有puts规范失败 – 你已经find了这个微妙的。

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

也许这只是我?

让它作为其本质上的一个function。 还有它的caching。

我立即发现了一个问题,让…在正在评估变化的Spec块中。

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

你需要确保打电话让你的期望块之外。 即你在调用Block中调用FactoryGirl.create。 我通常通过validation对象是否持久来做到这一点。

 object.persisted?.should eq true 

否则,当第一次调用let块时,由于懒惰的实例化,数据库中的更改实际上会发生。

更新

只需添加一个便条。 小心打代码高尔夫或在这种情况下rspec高尔夫与这个答案。

在这种情况下,我只需要调用一些对象响应的方法。 所以我调用.persisted? 对象的方法就像它的真理一样。 所有我试图做的是实例化对象。 你可以叫空吗? 或无? 太。 重点不是testing,而是通过调用它来使对象生命。

所以你不能重构

 object.persisted?.should eq true 

成为

 object.should be_persisted 

因为这个对象还没有被实例化…它的懒惰。 🙂

更新2

利用让! 即时对象创build的语法 ,这应该完全避免这个问题。 请注意,虽然它将打败很多懒惰的懒惰的目的。

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

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

Joseph的注意事项 – 如果你正在创build一个before(:all)数据库对象,那么它们将不会在事务中被捕获,而且更有可能在你的testing数据库中留下痕迹。 之前使用(:每个)。

使用let和懒惰评估的另一个原因是你可以通过覆盖上下文中的一个复杂的对象来testing各个部分,就像这个非常人为的例子:

 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 

“之前”默认意味着之前(:每个)。 参考Rspec书,版权2010年,第228页。

 before(scope = :each, options={}, &block) 

我之前使用(:each)为每个示例组播放一些数据,而不必调用“let”方法在“it”块中创build数据。 在这种情况下,在“it”块中代码较less。

如果我想在某些示例中使用某些数据,而不使用其他数据,则使用“let”。

以前和让我们都干掉“it”块。

为了避免混淆,“let”与之前(:all)不一样。 “让”重新评估每个示例(“it”)的方法和值,但在同一示例中跨多个调用caching值。 您可以在这里阅读更多关于它的信息: https : //www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let