【问题标题】:Rails - RSpec - Difference between "let" and "let!"Rails - RSpec - “让”和“让!”之间的区别
【发布时间】:2012-04-16 11:17:08
【问题描述】:

我已经阅读了RSpec manual 所说的差异,但有些事情仍然令人困惑。包括“The RSpec Book”在内的所有其他来源都只解释了“let”,而“The Rails 3 Way”与手册一样令人困惑。

我了解“let”仅在调用时进行评估,并在范围内保持相同的值。因此,在manual 的第一个示例中,第一个测试通过,因为“let”仅被调用一次,第二个测试通过,因为它添加到第一个测试的值(在第一次测试,值为 1)。

接着,因为“让!”在定义时评估,并在调用时再次评估,测试是否应该失败,因为“count.should eq(1)”应该是“count.should eq(2)”?

任何帮助将不胜感激。

【问题讨论】:

    标签: ruby rspec rspec2


    【解决方案1】:

    我通过一个非常简单的示例了解了letlet! 之间的区别。让我先阅读文档句子,然后动手展示输出。

    About let 医生说:-

    ...let惰性评估:直到第一次才评估 它定义的方法被调用。

    我理解以下示例的区别:-

    $count = 0
    describe "let" do
      let(:count) { $count += 1 }
    
      it "returns 1" do
        expect($count).to eq(1)
      end
    end
    

    让我们现在运行它:-

    arup@linux-wzza:~/Ruby> rspec spec/test_spec.rb
    F
    
    Failures:
    
      1) let is not cached across examples
         Failure/Error: expect($count).to eq(1)
    
           expected: 1
                got: 0
    
           (compared using ==)
         # ./spec/test_spec.rb:8:in `block (2 levels) in <top (required)>'
    
    Finished in 0.00138 seconds (files took 0.13618 seconds to load)
    1 example, 1 failure
    
    Failed examples:
    
    rspec ./spec/test_spec.rb:7 # let is not cached across examples
    arup@linux-wzza:~/Ruby>
    

    为什么是 错误 ?因为,正如 doc 所说,对于 let直到第一次调用它定义的方法时才会对其进行评估。示例中,我们没有调用count,因此$count 仍然是0,不会增加1

    现在来到let! 部分。医生说

    ....你可以使用 let!在每个示例之前强制调用方法。这意味着即使您没有在 example 中调用 helper 方法,它仍然会在您的示例运行之前被调用。

    让我们也测试一下:-

    这是修改后的代码

    $count = 0
    describe "let!" do
      let!(:count) { $count += 1 }
    
      it "returns 1" do
        expect($count).to eq(1)
      end
    end
    

    让我们运行这段代码:-

    arup@linux-wzza:~/Ruby> rspec spec/test_spec.rb
    .
    
    Finished in 0.00145 seconds (files took 0.13458 seconds to load)
    1 example, 0 failures
    

    看,现在$count 返回1,因此测试通过了。它发生在我使用let! 时,它在示例运行之前运行,尽管我们没有在示例中调用count

    这就是letlet! 的不同之处。

    【讨论】:

    • 请记住,在块之前的 a 中永远不要有 let 块,这就是 let!是为。欲了解更多信息,请查看此页面:kolosek.com/rspec-let-vs-before
    【解决方案2】:

    你可以阅读更多关于这个here,但基本上。 (:let) 是惰性求值的,如果你不调用它就永远不会被实例化,而 (:let!) 在每个方法调用之前都会被强制求值。

    【讨论】:

    • 谢谢贾斯汀。那是我不理解的“手册”。我的问题更多地集中在为什么该链接中的“第二规范”通过“count.should eq(1)”而不是“count.should eq(2)”作为我的预期。如果在方法调用之前评估它,并且在调用“count.should”时再次评估,那么第二个示例不应该等于 2 而不是 1?
    • halle-flippin-lujah,终于有了一个关于记忆或不管是什么的解释。简单明了,一切都应该如此。
    【解决方案3】:

    它不是在定义时调用,而是在每个示例之前调用(然后它被记忆并且不再被示例调用)。这样,count 的值为 1。

    无论如何,如果您有另一个示例,则再次调用 before 挂钩 - 以下所有测试都通过:

    $count = 0
    describe "let!" do
      invocation_order = []
    
      let!(:count) do
        invocation_order << :let!
        $count += 1
      end
    
      it "calls the helper method in a before hook" do
        invocation_order << :example
        invocation_order.should == [:let!, :example]
        count.should eq(1)
      end
    
      it "calls the helper method again" do
        count.should eq(2)
      end
    end
    

    【讨论】:

    • 同意,这就是第一个规范有意义的原因。当然,第二个规范会在示例之前进行评估(使其等于 1),然后在调用“count.should”时再次评估(使其等于 2)?
    • 不,它已经被调用和记忆,所以 $count 不再增加。无论如何,如果您有另一个示例,则再次调用 before 钩子。请参阅我编辑的答案,我添加了一些代码以进行澄清。
    • 那么这意味着如果您使用“let”并且规范(“it”)不执行“count.should”(或类似),那么增量不会发生?如果是这样,那么“let”不应该被认为是“之前”,因为默认情况下之前意味着“let!”的功能。还是我又错过了什么?
    • 这是记忆,不是记忆。来自 Obie Fernandez 的书“Memoized 意味着与 let 关联的代码块执行一次并存储以供将来调用,从而提高性能。”
    • 复制文档示例代码首先是问题的一部分。因此这里并没有太大帮助。
    【解决方案4】:

    我也认为这很令人困惑,但我认为 The Rails 3 Way 中的示例很好。
    let 类似于 before 块中的实例变量,而 let!立即被记住

    来自 Rails 3 方式

    describe BlogPost do
      let(:blog_post) { BlogPost.create :title => 'Hello' }
      let!(:comment) { blog_post.comments.create :text => 'first post' }
    
      describe "#comment" do
        before do
         blog_post.comment("finally got a first post")
        end
    
        it "adds the comment" do
          blog_post.comments.count.should == 2
        end
      end
    end
    

    "因为注释块永远不会被第一次执行 断言如果你使用了一个 let 定义,那么只有一个评论会有 即使实现可能正在运行,也已在此规范中添加。 通过使用 let!我们确保创建初始评论和规范 现在会过去的。”

    【讨论】:

      【解决方案5】:

      我也被letlet! 弄糊涂了,所以我从here 中拿了文档代码并玩了一下: https://gist.github.com/3489451

      希望对你有帮助!

      【讨论】:

        【解决方案6】:

        还有一种方法可以让您的规格保持可预测性。

        您几乎应该始终使用let。你不应该使用let!,除非你有意在示例中缓存值。这就是为什么:

        describe '#method' do
          # this user persists in the db across all sub contexts
          let!(:user) { create :user }
        
          context 'scenario 1' do
            context 'sub scenario' do
              # ...
              # 1000 lines long
              # ...
            end
        
            context 'sub scenario' do
              # you need to test user with a certain trait
              # and you forgot someone else (or yourself) already has a user created
              # with `let!` all the way on the top
              let(:user) { create :user, :trait }
        
              it 'fails even though you think it should pass' do
                # this might not be the best example but I found this pattern
                # pretty common in different code bases
                # And your spec failed, and you scratch your head until you realize
                # there are more users in the db than you like
                # and you are just testing against a wrong user
                expect(User.first.trait).to eq xxx
              end
            end
          end
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-07-21
          • 1970-01-01
          • 1970-01-01
          • 2013-06-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多