Day 20 真真假假的 Instance doubles

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

昨天有提到会稍微介绍一下 allow method,其实在昨天的范例里面就有使用到。

使用的时机在於允许 double 接受方法以及回传值。

RSpec.describe 'allow method review' do
  it "can customize return value for methods on doubles" do
    calculator = double
    
    allow(calculator).to receive(:add).and_return(15)
    
    expect(calculator.add).to eq(15)
    expect(calculator.add(3)).to eq(15)
    expect(calculator.add(132353123)).to eq(15)
  end
end

可以看到一个 calculator 被允许接受一个 add 方法,然後回传值为 15。

但这当然不是一个好的示范,写死你的回传值,我猜不是什麽有趣的事情!

接着我们要来说说昨天提到的 double method 有什麽坏处。

首先,整个 double 物件都是写死的,这可能会在开发的测试上造成一些不必要的压力,因为我们必须时时刻刻注意这个 double 物件的方法有没有更动。

若是我们不慎移除掉真实的 Code,他依旧会通过测试,因为我们的测试里面还是保有这个方法,但他不是真实的,一切都是捏造的!

还记得一开始用 double 的想法就是希望我们可以抛开其他关联的干扰,专注在我们要测试的方法上面,但这有可能害我们错失找到 Bug 的关键!

所以我们需要更严谨一些,於是就有了 Instance Doubles

Instance Doubles

所以说,为什麽要严谨一些呢?

class Burger
end

RSpec.describe Burger do
  it "#yummy" do
    burger = double(yummy: "Yum!")
    expect(burger.yummy).to eq("Yum!")
  end
end

上面这个会是成功通过的测试,当面临重构的情况,不小心移除掉某些程序码时,测试依旧会通过,一切看起来安然无恙,但真的会坏掉...

所以才有了 instance doubles 的出现,他会帮助你检查你真实的程序码,创造彼此的连结!

我们用 instance doubles 来试试看!

class Burger
end

RSpec.describe Burger do
  it "#yummy" do
    burger = instance_double(Burger, yummy: "Yum!")
    expect(burger.yummy).to eq("Yum!")
  end
end

他喷错了,意思就是这个类别没办法实施这个 yummy 实体方法,因为我们没有~

补上我们类别内的实体方法:

class Burger
  def yummy
    "Yum!"
  end
end

RSpec.describe Burger do
  it "#yummy" do
    burger = instance_double(Burger, yummy: "Yum!")
    expect(burger.yummy).to eq("Yum!")
  end
end

通过测试啦~

整个用法就是呢:

something = instance_double("你要测试的类别", "他的实体方法",.....)

他就会去帮你检查到底是不是真的有这个方法,不管你是写多写少,都会被抓出来,不要想要乱写方法进去,他会知道的!

在上一些用 allow method 的范例来看看吧

class Burger
  def yummy
    "Yum!"
  end
end

RSpec.describe Burger do
  it "#yummy" do
    burger = instance_double(Burger)
    allow(burger).to receive(:yummy).and_return("Yum!")
    expect(burger.yummy).to eq("Yum!")
  end
end

其实概念就和 double 非常相似,但就是多加了一个类别上去,让 RSpec 去帮你检查,所以我在这边可以说,如果要使用 double 请用 instance double,小心驶得万年船~

等等?你问我说只有实体变数有 double 吗?

那类别有吗?那是废话...当然有啊~

Class Doubles

我们一直强调使用 double 的情境,就是在於当你今天测试的目标会遭受其他的干扰时,我们就会利用 double 来专注在我们要测的项目上!

所以我们来创造一个使用到 class method 的情境:

class Burger
  def make
    "Make!"
  end
end

class BurgerStore
  attr_reader :burgers
  
  def sale
    @burgers = Burger.make
  end
end

接着我们想要对於 BurgerStoresale 方法来写测试:

RSpec.describe BurgerStore do
  it "can only implement class methods defined on a class" do
    burger_klass = class_double(Burger)
    expect(burger_klass).to receive(:make).and_return("Make!")
    expect(subject.sale).to eq("Make!")
  end
end

用我们刚刚学过关於 double 的知识来测试看看吧,一切看起来都非常合理呢~

看看是哪里出了问题?他説我们期待这个 class method 应该接受到一个 make 方法。

但我看看自己的原始码,在 BurgerStore 确实有 Burger.make 这段程序码发生啊?

哎呀,原来是因为我们还需要一个方法叫做 as_stubbed_const 接在这个 double 的後方,让我们使用这个 double 物件去确实的取代程序码内的 Burger.make 的这个 Burger

这个 as_stubbed_const 的用意就很像绑定这个 double 物件去取代真实的程序码,这时候测试就会通过了,因为他确实接收到 make 这个方法的呼叫~

我们可以在用一段示范来看看 as_stubbed_const 的意义到底在哪里?

class Burger
  def make
    "Make!"
  end
end

class BurgerStore
  attr_reader :burgers
  
  def sale
    @burgers = Burger.make
  end
end

RSpec.describe BurgerStore do
  it "can only implement class methods defined on a class" do
    burger_klass = class_double(Burger).as_stubbed_const
    expect(burger_klass).to receive(:make).and_return([1,2,3])
    expect(subject.sale).to eq([1,2,3])
  end
end

看得出来发生什麽了吗?我们原始码的回传值是 "Make!",但现在接受的却是 [1,2,3] 就是因为 as_stubbed_const 的绑定,但他其实和 instance_double 是一样聪明的喔,对於你写入的方法会有所检查~所以不要乱写!

小结

今天把 double 大概都介绍了一遍,明天将会介绍 spies 也是 mock 伪装军团的一员~


<<:  Day19 浅谈AR眼镜 未来可能取代手机的物品

>>:  [Day 19] 实作-美化首页 上传Git

乔叔教 Elastic - 27 - Elasticsearch 的优化技巧 (1/4) - Indexing 索引效能优化

Elasticsearch 的优化技巧 系列文章索引 (1/4) - Indexing 索引效能优化...

Material UI in React [Day9] Inputs (Radio) 单选 & (Switch) 开关

Radio 当用户需要查看所有可用选项时使用单选按钮,如果可用选项可以折叠,请考虑使用下拉菜单(Se...

Day23_控制项(A18遵循性) -2021/10/06

阿呜~第23天了,再撑一个星期~XDD" ▉A.18 Compliance 遵循性 所有的...

Day 16 Flask 前端

本来前端应该要更早点讲的,不过 Flask 的前端有了传入的值的话,可以有更多的操作,所以就放到这边...

Day 25 - 云端备份是降低专案风险的一环

一定要强调一下资料备份的重要性, 分享一个亲身经历的实际案例, 因为我本身还算是ASUS的爱用者,...