【问题标题】:How much time does "let" really save in RSpec tests?“让”在 RSpec 测试中真正节省了多少时间?
【发布时间】:2018-12-28 18:20:03
【问题描述】:

我发现在我的代码中设置一个变量比使用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

【问题讨论】:

  • 让我们提供一种共享代码的方法,并且依赖项是惰性评估和线程安全的。从你的例子来看,你没有正确使用它们 - 所以我会在你解雇它们之前尝试一下。
  • second @Anthony 我曾同样对let 感到沮丧,这有点令人困惑,因为您似乎在测试之外声明变量。您可以将let 放在describecontext 块内,但不能放在itbefore 块内。基本上,这会创建一个为每个测试创建新的可重用变量,这有助于防止测试之间的数据库工件,并具有延迟加载的额外好处,它只在调用时创建变量。这可能会产生看似“挑剔”的行为,但它确实在做它应该做的事情。 :)

标签: rspec performance-testing rspec-rails let


【解决方案1】:

你用错了这个东西,我理解你的沮丧。因此,让我给你一份在 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

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

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 中的一个保留概念,它是一个“你测试的东西”,所以你可以用 `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

关于您遇到的错误...

letsubject 声明不打算被调用 before(:context) 钩子,因为它们的存在是为了定义状态 在每个示例之间重置,而 before(:context) 存在于
定义在示例组中的示例之间共享的状态。

你正在做类似的事情

before do
  let(:x) { ... }
end

别这样,你在describecontext里​​面定义let,但是你可以在beforespecify里面使用它们(不定义,使用定义的):

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

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

【讨论】:

  • 经过深思熟虑的解释,应该被接受
  • 啊,所以let 对于设置不同的测试场景很有用,而不会以可能破坏其他测试的方式更改共享变量的状态。我认为有点像将变量传递给一个小函数并创建它自己的范围。它似乎也减少了对新变量名的需求,因为我们可以在不同的上下文中重用相同的符号。
  • 另请注意,单行语法允许您将“expect(subject)”替换为“is_expected”或将“expect(subject).to”替换为“should”,例如,subject { 3 };它 { 应该 eq(3) };它 { is_expected.to eq(3) }
猜你喜欢
  • 2010-10-01
  • 1970-01-01
  • 2016-07-12
  • 2012-07-28
  • 2018-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多