45

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

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

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

任何帮助,将不胜感激。

4

6 回答 6

62

我通过一个非常简单的例子理解了let和之间的区别。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$count01

现在来到这个部分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
.

Finished in 0.00145 seconds (files took 0.13458 seconds to load)
1 example, 0 failures

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

这就是彼此的方式let和不同之处。let!

于 2014-06-30T18:23:44.500 回答
29

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

于 2012-04-16T15:25:54.270 回答
15

它不是在定义时调用,而是在每个示例之前调用(然后它被记忆并且不再被示例调用)。这样,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
于 2012-04-16T15:29:52.253 回答
5

我也认为这很令人困惑,但我认为 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!我们确保创建了初始注释现在规范将通过。”

于 2013-02-04T22:25:52.983 回答
2

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

希望能帮助到你!

于 2012-08-28T03:44:37.270 回答
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
于 2018-01-31T21:43:26.117 回答