该文章同步发布於:我的部落格
昨天有提到会稍微介绍一下 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
。
所以说,为什麽要严谨一些呢?
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
吗?
那类别有吗?那是废话...当然有啊~
我们一直强调使用 double
的情境,就是在於当你今天测试的目标会遭受其他的干扰时,我们就会利用 double
来专注在我们要测的项目上!
所以我们来创造一个使用到 class method 的情境:
class Burger
def make
"Make!"
end
end
class BurgerStore
attr_reader :burgers
def sale
@burgers = Burger.make
end
end
接着我们想要对於 BurgerStore
的 sale
方法来写测试:
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
伪装军团的一员~
Elasticsearch 的优化技巧 系列文章索引 (1/4) - Indexing 索引效能优化...
Radio 当用户需要查看所有可用选项时使用单选按钮,如果可用选项可以折叠,请考虑使用下拉菜单(Se...
阿呜~第23天了,再撑一个星期~XDD" ▉A.18 Compliance 遵循性 所有的...
本来前端应该要更早点讲的,不过 Flask 的前端有了传入的值的话,可以有更多的操作,所以就放到这边...
一定要强调一下资料备份的重要性, 分享一个亲身经历的实际案例, 因为我本身还算是ASUS的爱用者,...