"let" 在 RSpec 测试中真正节省了多少时间?

How much time does "let" really save in RSpec tests?

我发现在我的代码中设置变量比使用 let 更容易。 let 很挑剔,总是告诉我错误的使用方式。

当我在规范中使用简单的变量声明时,例如

tx_good = makeTransaction1() ,一切正常。

但是当我像这样使用let

let(:tx_good) { makeTransaction1() } 我总是会遇到这样的错误,告诉我它不能在这里或那里...

  `let` and `subject` declarations are not intended to be called
   in a `before(:context)` hook, as they exist to define state that
   is reset between each example, while `before(:context)` exists to
   define state that is shared across examples in an example group.

鉴于使用 let 是多么挑剔,我不得不怀疑是否值得为使用它而付出额外的努力和关心。有谁知道使用 let 与仅仅预先分配一个变量相比,真正节省了多少处理时间?

我想遵循良好的测试协议,所以我希望有人能说服我为什么我应该像(看似)其他人一样使用 let

你用错了这些东西,我理解你的沮丧。所以让我给你一个在 RSpec.

中使用 lets 的简明手册

使用 let 的主要价值并非来自节省的处理能力。它是更广泛的 RSpec 理念的组成部分。我会尽力解释,希望你能更容易地进步...

let懒惰

当且仅当它在规范中实际使用时,您在块中定义的内容才会被调用:

context do
  let(:foo) { sleep(10000) } # will not happen
  specify { expect(1).to eq(1) }
end 

context do 
  specify do 
     foo = sleep(10000) # you'll wait
     expect(1).to eq(1)
  end
end

使用 let!,它是 let

的急切(即不懒惰)版本

let 已记忆

块内定义的任何内容都只会发生一次(在上下文范围内):

context do
  let(:random_number) { rand }
  specify do
    expect(random_number).to eq(random_number) # will always pass
  end
end

如果你不想要这个功能,定义一个方法:

context do
  def random_number
    rand
  end
  specify do
    expect(random_number).to eq(random_number) # sometimes pass, mostly fail
  end
end

let 在较低级别的上下文中覆盖来自较高级别的 let 定义:

context do
   let(:x) { 1 }
   specify { expect(x).to eq(1) # pass

   context 'with different x' do 
     let(:x) { 2 }
     specify { expect(x).to eq(2) # pass
   end

   context do
     specify { expect(x).to eq(1) # pass
   end
end

^ 这允许您以某种方式编写规范,在上下文中仅提及相关的 "pieces" 设置,例如:

context do 
   let(:x) { 1 }
   let(:y) { 1 }
   let(:z) { 1 }
   specify { expect(foo(x, y, z)).to eq(3) }

   context 'when z is nil'
     let(:z) { nil }
     specify { expect(foo(x, y, z)).to raise_error) } # foo doesn't work with z = nil
   end

   context 'when x is nil'
     let(:x) { nil }
     specify { expect(foo(x, y, z)).to eq(15) } 
   end
end

奖金:subject 是一个魔法 let

# writing 
subject { foo(x) }
# is almost the same as writing 
let(:subject) { foo(x) }

subject 是 RSpec 中的一个保留概念,它是一个 "thing you test" 所以你可以用 `foo(x, y, z) 这样写例子:

context do 
   let(:x) { 1 }
   let(:y) { 1 }
   let(:z) { 1 }
   subject { foo(x, y, z) }
   specify { expect(subject).to eq(3) }

   context 'when z is nil'
     let(:z) { nil }
     specify { expect(subject).to raise_error) } # foo doesn't work with z = nil
   end

   context 'when x is nil'
     let(:x) { nil }
     specify { expect(foo(subject)).to eq(15) } 
   end
end

关于您的错误...

let and subject declarations are not intended to be called in a before(:context) hook, as they exist to define state that is reset between each example, while before(:context) exists to
define state that is shared across examples in an example group.

你正在做类似

的事情
before do
  let(:x) { ... }
end

干脆不要做了,你在describecontext里面定义了let,但是你可以在[=里面使用它们(不定义它们,使用定义的东西) 37=] 和 specify:

let(:name) { 'Frank' }
before do
  User.create name: name
end

specify do
   expect(User.where(name: name).count).to eq(1)
end