Ruby:Rspec双重困惑

时间:2018-12-15 05:27:49

标签: ruby rspec

一个失败的测试让我质疑我对双重测试(测试框架是RSpec)的理解。

据我了解:

  

模拟是代表被测对象协作者的伪造对象

所以可以说我有一个Person类:

class Person 

  def default_number_of_products 
    Product.new 
  end

end

Product类:

class Product
  def initialize 
    @default_number = 3
  end
end

要测试在Person上调用default_number_of_products是否会收到产品new,我编写了一个如下所示的测试:

RSpec.describe Person do 
  let(:person) { Person.new }

  describe '#default_number_of_products' do     
    it 'invokes new on product' do 
      product = double(Product)
      expect(product).to receive(:new)
      person.default_number_of_products
    end
  end
end

失败并返回此错误:

// ♥ rspec spec/lib/person_spec.rb 
F

Failures:

  1) Person#default_number_of_products invokes new on product
     Failure/Error: expect(product).to receive(:new)

       (Double Product).new(*(any args))
           expected: 1 time with any arguments
           received: 0 times with any arguments
     # ./spec/lib/person_spec.rb:23:in `block (3 levels) in <top (required)>'

Finished in 0.01006 seconds (files took 0.1052 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/lib/person_spec.rb:21 # Person#default_number_of_products invokes new on product

另一方面,下面的测试通过:

RSpec.describe Person do 
  let(:person) { Person.new }
  let(:product) { Product }

  describe '#default_number_of_products' do   
    it 'invokes new on product' do  
      expect(product).to receive(:new)
      person.default_number_of_products
    end
  end
end

问题

  • 我认为可以通过使用double来消除对协作对象的依赖-这样,可以编写测试而无需实现协作类。上面的结果指出 我的理解不太正确。我想念的是什么?

2 个答案:

答案 0 :(得分:0)

您已经关闭。这是模拟对Person.new的调用的方法:

首先,让我们将PersonProduct类变得更加现实...

class Person 
  def default_number_of_products 
    Product.new.default_number
  end
end

class Product
  attr_reader :default_number

  def initialize
    @default_number = 3
  end
end

现在进行模拟Person类的Product测试...

describe Person do
  subject(:person) { described_class.new }

  describe '#default_number_of_products' do
    let(:product) { instance_double(Product, default_number: 42) }

    before do
      allow(Product).to receive(:new).and_return(product)
    end

    it 'returns 42' do
      expect(person.default_number_of_products).to eq(42)
    end
  end
end

如果执行了Product中的代码,则对person.default_number_of_products的调用将返回3。取而代之的是,该测试在Product.new上进行了监视,并返回了一个代替实际Product的双精度值。因此,执行person.default_number_of_products中的代码时,它会看到double,它的default_number为42。

最后,在上面的问题中,您提到您认为模拟应该允许您创建一个类而不必创建协作者。这是一个真实的声明。但是,在上面的测试中,instance_double(Product, ...)实际上是从真实的Product类创建一个鸭子类型。因此,需要对其进行定义。如果要在创建Product类之前进行TDD,则可以传递一个字符串来代替类名,如下所示:

let(:product) { instance_double('product', default_number: 42) }

HTH

答案 1 :(得分:0)

double只是您创建的用于与测试交互的对象,您可以使用class_double来充当“类”。当您编写product = double(Product)时,它会创建一些测试变量product,但不会替换现有的Product类。

来自:https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles/using-a-class-double

  

class_double是对instance_double的补充,其中包含   区别在于它验证给定类上的类方法,而不是   比实例方法。

     

此外,它还提供了一种方便的方法as_stubbed_const来   用定义的double替换具体的类

这将起作用:

it 'invokes new on product' do 
  product = class_double(Product).as_stubbed_const
  expect(product).to receive(:new)
  person.default_number_of_products
end