评估的Rspec {}使用最后一个变量值

时间:2012-09-21 23:16:47

标签: ruby testing rspec

我正在使用Rspec为我的程序进行一些测试。在规范中,我将类实例化一次,并使用 describe contexts 对其执行测试。如果它的似乎在上下文结束时被评估,我遇到了一些有趣的东西。例如,给出以下类及其相关规范:

class Tmp
  def initialize
    @values = {}
  end

  def modify(new_value1, new_value2)
    @values = {:a => new_value1, :b => new_value2}
  end

  def get_values
    return @values
  end
end

describe Tmp do
  tmp = Tmp.new()

  describe "testing something" do
    context "change value" do

      # First evaluation
      tmp.modify("hi", "bye")
      it {tmp.get_values.should == {:a => "hi", :b => "bye"}}

      # Second evaluation
      tmp.modify("bye", "hi")
      it {tmp.get_values.should == {:a => "bye", :b => "hi"}}
    end
  end
end

使用提供的类和规范,结果如下:

F.

Failures:

  1) Tmp testing something change value 
     Failure/Error: it {tmp.get_values.should == {:a => "hi", :b => "bye"}}
       expected: {:a=>"hi", :b=>"bye"}
            got: {:a=>"bye", :b=>"hi"} (using ==)
       Diff:
       @@ -1,3 +1,3 @@
       -:a => "hi",
       -:b => "bye"
       +:a => "bye",
       +:b => "hi"
     # ./spec/tmp_spec.rb:11:in `block (4 levels) in <top (required)>'

Finished in 0.00211 seconds
2 examples, 1 failure

这很有趣,因为Rspec似乎用 tmp 中的值来评估第一个 it ,因为它位于上下文的末尾。即使在上下文中 tmp 正在更改其值并且应该通过,Rspec也会根据变量在其中的最后值来评估 。结束(我甚至尝试使用上下文中的本地原始变量并具有类似的经验)。

有没有办法绕过这个并按顺序评估 ?或者至少要通过以下测试?我知道我可以使用不同的变量,它会起作用,但必须有办法解决这个问题。我也想知道这是否是对Rspec的预期效果。

有关菲利普回答的更新

通过在单个 it 块中进行更改,规范通过:

describe Tmp do
  describe "do something" do
    let(:instance) {Tmp.new}

    it 'should be modifiable' do
      instance.modify('hi', 'bye')
      instance.values.should == {a: 'hi', b: 'bye'}
      instance.modify('bye', 'hi')
      instance.values.should == {a: 'bye', b: 'hi'}
    end
  end
end

但是,如果我使用主题,它似乎还原并在第一个应该

上失败
describe Tmp do
  describe "do something" do
    let(:instance) {Tmp.new}
    subject{instance.values}

    it 'should be modifiable' do
      instance.modify('hi', 'bye')
      should == {a: 'hi', b: 'bye'}
      instance.modify('bye', 'hi')
      should == {a: 'bye', b: 'hi'}
    end
  end
end

不确定为什么会这样。至少我看到更改应该在 it 块中,以更好地反映我们正在测试的更改。

3 个答案:

答案 0 :(得分:5)

您不应该在itspecifybeforeletsubject块之外创建实例并对其进行操作。否则,在测试后不会重置主题和其他变量。

下面我用几种不同的风格重写了你的规范。请参阅内联注释以获得解释。

class Tmp
  # Exposes the @values ivar through #values
  attr_reader :values

  def initialize
    @values = {}
  end

  def modify(new_value1, new_value2)
    @values = {a: new_value1, b: new_value2}
  end
end

describe Tmp do
  #`let` makes the return value of the block available as a variable named `instance` (technically it is a method named instance, but let's call it a variable).
  let(:instance) { described_class.new }

  # Inside an it block you can access the variables defined through let.
  it 'should be modifiable' do
    instance.modify('hi', 'bye')
    instance.values.should == {a: 'hi', b: 'bye'}
  end

  # or

  # `specify` is like `it` but it takes no argument:
  specify do
    instance.modify('hi', 'bye')
    instance.values.should == {a: 'hi', b: 'bye'}
  end

  # or

  # This is another common way of defining specs, one describe per method.
  describe '#modify' do
    let(:instance) { described_class.new }
    # Here we define the subject which is used implicitly when calling `#should` directly.
    subject { instance.values }
    before { instance.modify('hi', 'bye') }
    it { should == {a: 'hi', b: 'bye' } # Equivalent to calling `subject.should == ...`
  end

  # or

  # When we don't specify a subject, it will be an instance of the top level described object (Tmp).
  describe '#modify' do
    before { subject.modify('hi', 'bye') }
    its(:values) { should == {a: 'hi', b: 'bye' }
  end
end

答案 1 :(得分:1)

来自rspec-core docs:

  

在幕后,示例组是块传递的类   评估描述或上下文。传递给它的块是   在该类的实例的上下文中进行评估。

https://www.relishapp.com/rspec/rspec-core/docs/example-groups/basic-structure-describe-it

这向我建议,在实例化组时,将执行ExampleGroup中的代码(描述块),但示例本身(它阻止)除外。然后在描述块的上下文中执行它。这就是为什么它只能看到tmp的最后一个值。

答案 2 :(得分:1)

由于您modify的实施与let&amp;行为之间的互动,您的基于主题的示例失败了subject

这两种方法缓存了他们调用的结果 - 你显然不希望每次引用instance时都会创建一个新的类实例。这意味着你应该使用subject的值,因为它是第一次被访问(由你或rspec明确地使用。

你的主题是instance.values,但是调用你的修改方法将导致instance.values成为一个新对象(你正在为@values分配一个新的哈希,而不是在适当的位置改变它)。你的断言使用了第一个使用的主题值,所以他们根本没有比较instance.values的当前值,因此你的规格失败了。

我个人认为主题是instance.values有点奇怪:你正在与之互动的是instance,这将是我选择的主题。