一个失败的测试让我质疑我对双重测试(测试框架是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
问题:
答案 0 :(得分:0)
您已经关闭。这是模拟对Person.new的调用的方法:
首先,让我们将Person
和Product
类变得更加现实...
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