Day 21 Spies 间谍来袭!

该文章同步发布於:我的部落格

今天我们要介绍 Mock 军团的最後一员,也就是 Spies 这个用法!

除了加上范例之外,两者间的差别,也会好好地来解释一番~

和 double 的差别

首先就是 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

其实有趣的地方在於,这边的 expectationhave_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 最好,以後都用他好了~

但最终要如何使用,还是取决於功能的考量,和团队的取向,只要能够好好的测试到需要测试的功能就好了!

所以你要说 spiesdouble 有真的一模一样吗?也没有啦其实,spies 再做得事情更像是潜入敌营,伪装成某一段程序码的感觉,而且 have_received 也真的需要接受的方法,只是需要回传值的话还是得使用 allow

还是有很大的不同在於撰写的模式,阅读起来的舒适度!但开心就好~

小结

明天开始为期大概 10 天会正式进入 rspec-rails 的章节!

也是 RSpec 真正的战场,刚好加上最近上班也都在写的缘故,希望可以把一些实际的案例放入文章中,让需要的人也能够看到!

基本的 Model 测试 Feature 测试 以及 Stub API 的方式都会尽量地写进去。

共勉之!


<<:  {DAY 23} Pandas 学习笔记part.9

>>:  Vue.js 从零开始:emit 元件的沟通

策略实作 - 葛兰碧八法

MoneyDJ理财网 - 葛兰碧八大法则 葛兰碧八法是根据移动平均线和股价之间的关系,来判断买入,卖...

【Day19】:PWM输出-模拟类比讯号

PWM-脉冲宽度调变 我相信很多人在使用Arduion的时候还是不清楚PWM到底在干嘛? PWM是一...

予焦啦!实作基本排程

本节是以 Golang 上游 6a79f358069195e1cddb821e81fab956d9...

总结 "不仅是程序码代管平台 - Github 能做些什麽?"

首先,我要先感谢老婆与家人的支持,让我有机会在 2021 年 ITHome 铁人赛完赛。其次感谢热心...