(译)RSpec 中的 Let,Let! 和 实例变量的比较

原文:Let vs. Let! vs. Instance Variables in RSpec
原文作者:Cezar Halmagean

很多人可能有这样的困惑,在 RSpec 中应该使用 let let! 还是 @ 开头的实例变量来共享变量(原文为 store state,这里译为共享变量)。

在本文中,我将尝试解释这三者的不同,你可以据此来判断使用哪一个。

当使用 let 的时候

下面是官方文档中 let 的定义(为便于理解就不翻译了 -.-

Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples. Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked.

这段话的意思是,如果你在一个测试用例(it块)中调用了多次,它只会在第一次求值,后续调用返回第一次执行的结果。

describe GetTime do
  let(:current_time) { Time.now }

  it "gets the same time over and over again" do
    puts current_time # => 2018-07-19 09:35:29 +0300
    sleep(3)
    puts current_time # => 2018-07-19 09:35:29 +0300
  end

  it "gets the time again" do
    puts current_time # => 2018-07-19 09:35:32 +0300
  end
end

如你所见,第一个用例中(即第一个it块),虽然两次调用之间延迟了3s,但是返回的值还是一样的。

这是因为第一次 current_time 调用后,它的返回值被缓存了下来,在这个块中再次调用 current_time 时,就会返回已缓存的值。

换句话说,{ Time.now } 只在每个测试用例(it)块中求值一次。

因此,当你在第二个 it 块中再次调用时,{ Time.now } 将会重新求值。然后就跟前面一样,这个值会被缓存以用于后续的调用。

惰性求值意味着 let 块只会在被调用时才开始执行。下面的例子可以证明这一点。

describe "GetTime" do
  let(:current_time) { Time.now }

  before(:each) do
    puts Time.now # => 2018-07-19 09:45:59 +0300
  end

  it "gets the time" do
    sleep(3)
    puts current_time # => 2018-07-19 09:46:02 +0300
  end
end

current_time 被定义时,{ Time.now } 并未立即求值,你可以对比 current_time 得到的值和 before 块中 { Time.now } 得到的值。

我们可能会认为 let 的作用是定义一个变量,但事实上它是先定义一个定义变量的方法,然后在被调用时才初始化该变量。


当使用 let! 的时候

下面是 let! 的官方定义

You can use let! to force the method’s invocation before each example.

let let! 的区别在于: let! 会在一个隐含的 before 块中被调用,所以 let! it 块之前就已经被调用,并且缓存了下来

describe "GetTime" do
  let!(:current_time) { Time.now }

  before(:each) do
    puts Time.now # => 2018-07-19 09:57:52 +0300
  end

  it "gets the time" do
    sleep(3)
    puts current_time # => 2018-07-19 09:57:52 +0300
  end
end

所以尽管存在时间延迟,但不影响求值的结果,因为这个值已经在 before 块中求出并且缓存了。

当你需要在 it 块前设置一些状态时,这个行为会很有用。


实例变量

实例变量带来的问题是:当实例变量被引用时,就会被自动创建。所以即使你不小心拼写错了实例变量的名称,也不会收到一个报错。拼写错误的实例变量名称将会导致创建一个该名称的实例变量,其值为 nil

这将导致一些微妙并且难以追踪的错误

不过,你可以开启 warnings 设置,这样一来,在代码运行时如果发现实例变量名拼写错误将会抛出一个警告提示。

相比之下,使用 let 时,如果你使用了拼写错误的变量,程序将会抛出 NameError 的异常。

另一件需要注意的事情是,如果你在 before 块中初始化了一个实例变量,那么每个块执行前都会执行这个操作,无论你是否使用这个变量。(这是当然的)

所以如果这个初始化过程非常耗时的话,就会导致你的测试过程变慢

最后我们得出的结论是:当你想要惰性求值时使用 let ,反之使用 let! ,实例变量嘛...好吧,我找不到在你的测试中使用它的理由。

点赞

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注