一直对 stub 和 mock 不是非常理解,前几天听了Terry
和aNdReW
大侠的指点后感觉对它们的理解深入了许多,从不理解到理解这种恍然大悟的感觉非常爽,这里把自己粗浅的认识记录下来并分享给大家,希望对像我一样的新手有帮助
先从stub
说起,什么是stub
呢,CodeSchool
给出这样的定义:
Stub: For replacing a method with code that returns a specified result
简单说就是你可以用stub
去伪造 (fake) 一个方法,阻断对原来方法的调用,例如下面来自CodeSchool
的例子
我们有一个叫zombie
的model
class Zombie < ActiveRecord::Base
has_one :weapon
def decapitate
weapon.slice(self, :head)
self.status = "dead again"
end
end
我们要测试decapitate
方法,它里面调用了weapon
的slice
方法,下面是测试代码:
describe Zombie do
let(:zombie) { Zombie.create }
context "#decapitate" do
it "sets status to dead again" do
zombie.weapon.stub(:slice)
zombie.decapitate
zombie.status.should == "dead again"
end
end
end
上面代码的第 6 行就是用stub
伪造了weapon
的slice
方法,阻断了对原来方法的调用
你可能会问为什么我们要这样做,这是因为我们在做单元测试,weapon
的slice
可能会非常复杂,里面又调用了其它的方法等等,这是集成测试应该做的工作。事实上这里我们是在测试decapitate
方法会把zombie.status
设置成"dead again"
接下来我们来说mock
, CodeSchool
上给的定义是这样的:
Mock:
A stub with an expectations that the method gets called.
简单来说mock
就是stub + expectation
, 说它是stub
是因为它也可以像stub
一样伪造方法,阻断对原来方法的调用,expectation
是说它不仅伪造了这个方法,它还期望你 (必须) 调用这个方法,如果没有被调用到,这个test
就fail
了,看下面的例子
describe Zombie do
let(:zombie) { Zombie.create }
context "#decapitate" do
it "calls weapon.slice" do
zombie.weapon.should_receive(:slice)
zombie.decapitate
end
end
end
这里的第 6 行伪造了weapon
的slice
方法,并期望这个方法在这个测试中被调用。
你可能会想为什么要这样写,这是因为我们仅仅是要测试decapitate
这个方法确实调用了weapon.slice
, 可以把decapitate
想成下面的黑盒,我们蹲在图中的 A 点,等着看它会不会去调用weapon.slice
这个图是Sandi Metz
在RailsConf 2013
上的演讲The Magic Tricks of Testing
这里注意一下顺序,一般的测试先是执行一个动作,然后再去判断状态或其它东西,像前面stub
的例子,先调用 decapitate 方法,再去判断status
的变化,就好像我踢你一脚,看你会不会喊疼,而这里是先有期望再有动作,这就好比老板对你说这个下周前完成,不然就滚蛋一样
期待Terry
深入探讨 stub 和 mock 的文章