一直对 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 的文章