Rails - RSpec - “let”和“let!”之间的区别

时间:2012-04-16 11:17:08

标签: ruby rspec rspec2

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

我理解“let”仅在调用时进行评估,并在范围内保持相同的值。所以有意义的是,在manual的第一个例子中,第一个测试通过,因为“let”只被调用一次,第二个测试通过,因为它增加了第一个测试的值(已经评估过一次)在第一次测试中,其值为1)。

之后,因为“让!”在定义时进行评估,并在被调用时再次进行,如果测试没有失败,因为“count.should eq(1)”应该是“count.should eq(2)”?

任何帮助都将不胜感激。

6 个答案:

答案 0 :(得分:43)

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

About let doc说: -

  

... 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!在每个示例之前强制执行方法的调用。这意味着即使你没有在示例中调用 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!之间的差异。

答案 1 :(得分:25)

您可以阅读有关此here的更多信息,但基本上可以。 (:let)被懒惰地评估,如果你不调用它将永远不会被实例化,而(:let!)在每个方法调用之前被强制评估。

答案 2 :(得分:11)

在定义时不会调用它,而是在每个示例之前调用(然后它被记忆化,而不是由示例再次调用)。这样,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

答案 3 :(得分:4)

我也认为这很令人困惑,但我认为The Rails 3 Way的例子很好 let类似于before块中的实例变量,而let!立即备忘录

来自The Rails 3 Way

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定义,则只有一个注释   已添加到此规范中,即使实现可能正在运行。   通过使用让!我们确保创建初始评论和规范   现在会通过。“

答案 4 :(得分:2)

我也对letlet!感到困惑,所以我从here获取文档代码并使用它: https://gist.github.com/3489451

希望它有所帮助!

答案 5 :(得分:1)

这是一种保持规格可预测的方法。

你应该总是使用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