首页 文章

何时使用RSpec let()?

提问于
浏览
427

我倾向于在块之前使用来设置实例变量 . 然后,我在我的示例中使用这些变量 . 我最近遇到了 let() . 根据RSpec文档,它习惯了

...定义一个memoized帮助方法 . 该值将在同一示例中的多个调用中缓存,但不跨示例缓存 .

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

9 回答

  • 1

    重要的是要记住 let 是惰性评估的,不会在其中放置副作用方法,否则您将无法轻易地从 let 更改为 before(:each) . 您可以使用 let! 而不是 let ,以便在每个方案之前对其进行评估 .

  • 76

    使用实例变量和 let() 之间的区别在于 let()lazy-evaluated . 这意味着 let() 在第一次运行它定义的方法之前不会被评估 .

    beforelet 之间的区别在于 let() 为您提供了一种以'cascading'样式定义一组变量的好方法 . 通过这样做,通过简化代码,规范看起来更好一些 .

  • 580

    约瑟夫的注意事项 - 如果你在 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
    
  • 16

    let是功能性的,因为它本质上是一个Proc . 也是它的缓存 .

    我得到的一个问题就是......在一个正在评估变化的Spec块中 .

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

    您需要确保在预期阻止之外调用 let . 即你在let块中调用 FactoryGirl.create . 我通常通过验证对象是否持久来做到这一点 .

    object.persisted?.should eq true
    

    否则,当第一次调用 let 块时,由于延迟实例化而实际发生数据库更改 .

    Update

    只需添加备注 . 小心玩code golf或在这种情况下使用这个答案的rspec golf .

    在这种情况下,我只需要调用一个对象响应的方法 . 所以我在对象上调用 _.persisted? _方法作为它的真实 . 我所有的测试,但通过调用它来使对象栩栩如生 .

    所以你无法重构

    object.persisted?.should eq true
    

    成为

    object.should be_persisted
    

    因为对象尚未实例化...它的懒惰 . :)

    Update 2

    利用let! syntax来创建即时对象,这应该完全避免这个问题 . 注意虽然它会打败很多非撞击让懒惰的目的 .

    此外,在某些情况下,您可能实际上想要利用subject syntax而不是let,因为它可能会为您提供其他选项 .

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

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

    这使规格清晰可读 .

  • 13

    "before"默认隐含 before(:each) . Ref The Rspec Book,copyright 2010,page 228 .

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

    我使用 before(:each) 为每个示例组播种一些数据,而不必调用 let 方法在"it"块中创建数据 . 在这种情况下,"it"块中的代码更少 .

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

    之前和之前都非常适合干掉“它”块 .

    为避免混淆,"let"与 before(:all) 不同 . "Let"重新评估每个示例的方法和值("it"),但在同一示例中将值缓存到多个调用中 . 你可以在这里阅读更多相关信息:https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let

  • 5

    通常, let() 是一种更好的语法,它可以节省您在整个地方键入 @name 符号 . 但是,请注意!我发现 let() 也引入了微妙的错误(或者至少是头部划痕)因为变量在您尝试使用之前并不存在...告诉故事标志:如果在 let() 之后添加 puts 以查看变量是否正确允许规范通过,但没有 puts 规范失败 - 你已经发现了这个微妙之处 .

    我还发现 let() 似乎并没有在所有情况下缓存!我在我的博客中写了:http://technicaldebt.com/?p=1242

    也许只是我?

  • 2

    我在我的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,以及load-everything-in规范/支持/行为,您可以创建特定于您的应用程序的自己的spec mini-DSL . 我已经编写过针对Rack和RESTful资源进行测试的文章 .

    我使用的策略是Factory-everything(通过Machinist Forgery / Faker) . 但是,可以使用它与before(:each)块一起为整个示例组预加载工厂,允许规范更快地运行:http://makandra.com/notes/770-taking-advantage-of-rspec-s-let-in-before-blocks

  • 0

    出于以下几个原因,我总是更喜欢 let 到实例变量:

    • 实例变量在引用时才会存在 . 这意味着如果你手指操作实例变量的拼写,将创建一个新的并初始化为 nil ,这可能导致细微的错误和误报 . 由于 let 创建了一个方法,当你拼错它时你会得到 NameError ,我认为这更好 . 它也使得重构规范变得更容易 .

    • 一个 before(:each) 钩子将在每个例子之前运行,即使这个例子通常不是很重要,但如果实例变量的设置需要很长时间,那么你就是在浪费周期 . 对于 let 定义的方法,初始化代码仅在示例调用它时运行 .

    • 您可以直接将示例中的局部变量重构为let而不更改示例中的引用语法 . 如果重构实例变量,则必须更改示例中引用对象的方式(例如,添加 @ ) .

    • 这有点主观,但正如迈克刘易斯指出的那样,我认为它使规范更容易阅读 . 我喜欢用 let 定义所有依赖对象的组织,并保持我的 it 块好看又短 .

相关问题