该文章同步发布於:我的部落格
今天我们要介绍 Mock 军团的最後一员,也就是 Spies
这个用法!
除了加上范例之外,两者间的差别,也会好好地来解释一番~
首先就是 Spies
本身更狡诈了一些,还记得我们在写 double
或是 instance double
的时候,常常要在先去期望这个变数获得什麽样的方法,并写在他的後面~
像是这样:
RSpec.describe 'double' do
let(:store) { double('store', sell: 'earn the money!')}
it 'should invoke method first' do
expect(store.sell).to eq('earn the money!')
end
end
但 Spies
本身是可以先写方法出来的,就像正常的测试一样,只是 expectation
的部分需要做一些改造!
像是这样:
RSpec.describe 'spies' do
let(:burger) { spy('burger') }
it 'confirm a message has been received' do
burger.eat
expect(burger).to have_received(:eat)
end
end
其实有趣的地方在於,这边的 expectation
是 have_received
,有种在玩英文文法的感觉,这个汉堡已经接收到 eat
方法的概念!
而 Spies
也有他的检查机制存在,若是我们没有收到的方法,但却有的话会发生什麽事呢?
RSpec.describe 'spies' do
let(:burger) { spy('burger') }
it 'confirm a message has been received' do
burger.eat
expect(burger).to have_received(:eat)
expect(burger).to have_received(:throw)
end
end
这个变数期待着一次的 throw
方法,但却没有,所以被喷错了。
那至於这个和 instance double
之间要如何做选择呢?这边先卖个关子,後面会有有趣的实验来看看差别在哪里~
其实使用的情境和 instance double
基本上是很像的,都是为了独立某段程序码的干扰,让测试的独立性更好,更专注在我们要测试的部分。
而不是为了生出别的实体变数而搞得满目疮痍~
这边再用一段简单的程序码来叙述一下情境!
class Cheese
def initialize(type)
@type = type
end
end
class Burger
attr_reader :cheese
def initialize
@cheese = []
end
def add(type)
@cheese << Cheese.new(type)
end
end
这边很清晰的是我们要在汉堡里面加入 cheese
,就像前几天谈到的一样,我们要专注在 Burger
这个类别,所以我们希望可以把 第 15 行的 Cheese.new(type)
这段给独立出来,用 mock 的方式替代!
接着我们来写会通过的测试!
RSpec.describe Burger do
let(:cheese) { spy(Cheese) }
before do
allow(Cheese).to receive(:new).and_return(cheese)
end
describe '#add' do
it 'add cheese to burger' do
subject.add('parmesan')
expect(Cheese).to have_received(:new).with('parmesan')
expect(subject.cheese.length).to eq 1
expect(subject.cheese.first).to eq(cheese)
end
end
end
我们逐行来解释一下,首先在进入测试之前( 第五行 ),我们先做了允许 Cheese
这个类别接收 new
方法,并且 return 我们的 spy(Cheese)
接着进入测试的 block 中,subject
执行了 add
方法,并且传入参数 parmesan。
接着我们又说这个 Cheese
已经接受了 new
方法,也就是我们在 before
block 中所做的事情,在这个时候,我们已经彻底的替换掉正式的程序码了。
接着的 expectation
就是基本的操作罗~
但你会发现,这个 spy
替换成 instance_double
也能够正常的使用。
奇怪了... 那我们印出来看看吧!我们先印印看 spy
版本的
RSpec.describe Burger do
let(:cheese) { spy(Cheese) }
before do
allow(Cheese).to receive(:new).and_return(cheese)
end
describe '#add' do
it 'add cheese to burger' do
subject.add('parmesan')
expect(Cheese).to have_received(:new).with('parmesan')
p cheese # 印出来看看吧
expect(subject.cheese.length).to eq 1
expect(subject.cheese.first).to eq(cheese)
end
end
end
有没有搞错?这个 spies
讲得这麽厉害,但结果是 double
的语法糖衣吗?
我们快印看看 instance double
是什麽?
RSpec.describe Burger do
let(:cheese) { instance_double(Cheese) }
before do
allow(Cheese).to receive(:new).and_return(cheese)
end
describe '#add' do
it 'add cheese to burger' do
subject.add('parmesan')
expect(Cheese).to have_received(:new).with('parmesan')
p cheese # 印出来看看吧
expect(subject.cheese.length).to eq 1
expect(subject.cheese.first).to eq(cheese)
end
end
end
呜呜,骗我感情,最後还是 instance double
最好,以後都用他好了~
但最终要如何使用,还是取决於功能的考量,和团队的取向,只要能够好好的测试到需要测试的功能就好了!
所以你要说 spies
和 double
有真的一模一样吗?也没有啦其实,spies
再做得事情更像是潜入敌营,伪装成某一段程序码的感觉,而且 have_received
也真的需要接受的方法,只是需要回传值的话还是得使用 allow
。
还是有很大的不同在於撰写的模式,阅读起来的舒适度!但开心就好~
明天开始为期大概 10 天会正式进入 rspec-rails
的章节!
也是 RSpec 真正的战场,刚好加上最近上班也都在写的缘故,希望可以把一些实际的案例放入文章中,让需要的人也能够看到!
基本的 Model 测试 Feature 测试 以及 Stub API 的方式都会尽量地写进去。
共勉之!
<<: {DAY 23} Pandas 学习笔记part.9
MoneyDJ理财网 - 葛兰碧八大法则 葛兰碧八法是根据移动平均线和股价之间的关系,来判断买入,卖...
PWM-脉冲宽度调变 我相信很多人在使用Arduion的时候还是不清楚PWM到底在干嘛? PWM是一...
本节是以 Golang 上游 6a79f358069195e1cddb821e81fab956d9...
首先,我要先感谢老婆与家人的支持,让我有机会在 2021 年 ITHome 铁人赛完赛。其次感谢热心...