William's Blog with Octopress

Octopress is A blogging framework for hackers.

理解测试中的stub和mock

| Comments

刚开始学习rspec的时候,stubmock理解起来有点困难,听了TerryaNdReW的讲解后,对它们的理解深入多了,在此表示感谢

先从stub说起,什么是stub呢,CodeSchool给出这样的定义:

Stub:
For replacing a method with code that returns a specified result

简单说就是你可以用stub伪造(fade)一个方法,阻断对原来方法的调用,例如下面来自CodeSchool的例子

我们有一个叫zombiemodel

app/models/zombie.rb
1
2
3
4
5
6
7
8
class Zombie < ActiveRecord::Base
  has_one :weapon

  def decapitate
    weapon.slice(self, :head)
    self.status = "dead again"
  end
end

我们要测试decapitate方法,它里面调用了weaponslice方法,下面是测试代码:

/spec/models/zombie_spec.rb
1
2
3
4
5
6
7
8
9
10
11
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伪造了weaponslice方法,阻断了对原来方法的调用

你可能会问为什么我们要这样做,这是因为我们在做单元测试,weaponslice可能会非常复杂,里面又调用了其它的方法等等,这是集成测试应该做的工作.事实上这里我们是在测试decapitate方法会把zombie.status设置成"dead again"

接下来我们来说mock, CodeSchool上给的定义是这样的:

Mock:
A stub with an expectations that the method gets called.

简单来说mock就是stub + expectation, 说它是stub是因为它也可以像stub一样伪造方法,阻断对原来方法的调用, expectation是说它不仅伪造了这个方法,它还期望你(必须)调用这个方法,如果没有被调用到,这个testfail了,看下面的例子

/spec/models/zombie_spec.rb
1
2
3
4
5
6
7
8
9
10
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行伪造了weaponslice方法,并期望这个方法在这个测试中被调用.

你可能会想为什么要这样写,这是因为我们仅仅是要测试decapitate这个方法确实调用了weapon.slice, 可以把decapitate想成下面的黑盒,我们蹲在图中的A点,等着看它会不会去调用weapon.slice

这个图是Sandi MetzRailsConf 2013上的演讲The Magic Tricks of Testing

视频

Slides

这里注意一下顺序,一般的测试先是执行一个动作,然后再去判断状态或其它东西,像前面stub的例子,先调用decapitate方法,再去判断status的变化,就好像我踢你一脚,看你会不会喊疼,而这里是先有期望再有动作,这就好比老板对你说这个下周前完成,不然就滚蛋一样

Comments