该文章同步发布於:我的部落格
昨天结束了 Matcher
的介绍,今天开始进入 mock
的篇章。
还记得一开始提到的 unit test,我们希望着重在小的功能上进行测试,但一个 App 常常牵扯到各式各样不同的关联。
这时候的 unit test 就会变得很难写,因为你需要顾虑到许多不同的方法所回传的值,因为他们都会影响到你测试的结果!
这时候 mock
系列的功能就可以很有效地帮助到你,因为他就是为了假装、为了模仿而生!
从概念上来说,double
是一个制造假物件的方法,而这个假物件进而能够接受方法,设定好回传值。
我们先看看范例,再来解释:
RSpec.describe "double method" do
it "can defined method to be invoked" do
basketball_player = double("Lebron James", dunk: "Ah!!!!", shoot: "Goal!!!!")
expect(basketball_player.dunk).to eq("Ah!!!!")
end
end
我们 double
出了一个篮球员,可以灌篮然後发出 Ah!!!
的声音~
上面这段测试是成功通过的!那到底是在做什麽呢?
double
可以让我们预期的方法和回传的值,变成key-value
的组合,然後存放在这个 double
物件中。
接着他一样可以使用他身上的方法,然後得到预设好的回传值~
我们也可以用 allow
的写法,看起来会更直观一些,虽然比较长。
RSpec.describe "double method" do
it "can defined method to be invoked" do
basketball_player = double("Lebron James")
allow(basketball_player).to receive(:dunk).and_return("Ah!!!!")
expect(basketball_player.dunk).to eq("Ah!!!!")
end
end
我们允许
basketball_player
物件接收一个dunk
方法,然後回传Ah!!!!!
所以这个测试也是理所当然的会成功通过!
但这样一次写一个会不会太麻烦了?而且我又不想要写在初始化里面很乱...
还有另一个叫做 receive_messages
的方法喔!
RSpec.describe "double method" do
it "can defined method to be invoked" do
basketball_player = double("Lebron James")
allow(basketball_player).to receive_messages(dunk: "Ah!!!!", shoot: "Goal!!!!")
expect(basketball_player.dunk).to eq("Ah!!!!")
expect(basketball_player.shoot).to eq("Goal!!!!")
end
end
其实效果等同你在初始化的时候,直接写在後面是一样的道理,但我自己比较喜欢看 allow
的方法,会让人比较理解的感觉~
但其实上面的三种方式都可以,都只是建立 double
物件的手段~
而或许你还是觉得,所以这能干嘛?反正怎麽测都可以过,有意义吗?
当然不是叫你针对你要测试的物件做 double
,这样钱也太好赚了吧 ?
而是我们要被不同的类别所牵扯,导致我们需要 double
的帮忙。
接下来就会用范例来看看,什麽叫做被别的类别给影响,导致你需要使用 double
的情形~
class Cowboy
def initialize(name)
@name = name
end
def fighting?
true
end
def draw_the_gun
"Bang!!!"
end
def be_shot
"Help me..."
end
def continue?
false
end
end
class Bar
attr_reader :cow_boy
def initialize(cow_boy)
@cow_boy = cow_boy
end
def start_fighting
if cow_boy.fighting?
cow_boy.draw_the_gun
cow_boy.be_shot
cow_boy.continue?
end
end
end
从上面这个简单的西部牛仔剧情片编织的类别,可以看到酒吧以及牛仔之间的关系!
但当我们今天要写 Bar
这个类别的测试时怎麽办呢?
我们要测试 start_fighting
这个方法时,可以看到被 cow_boy
给影响到了!
但其实我们根本可以不用关心 cow_boy
是怎麽作业的?他的方法回传什麽那都不重要,我们要专注在现在这个方法应该要怎麽通过才对。
他的流程是不是正确的?这才是我们在乎的地方~
所以这就是前面提到,被影响到的时候,我们应该要用 double
来取代这边的 cow_boy
然後使我们的测试可以通过!
当然这是很搞笑的介绍,但事实上的专案中,就是会有许多不同的关系的存在,也都会影响到整个方法的进行。
接着我们来用 double
测试这个 Bar
类别的实体方法吧!
RSpec.describe Bar do
let(:cow_boy) { double("Gene Autry", fighting?: true, draw_the_gun: "Bang!!!", be_shot: "Help me...", continue?: true) }
subject { described_class.new(cow_boy) }
describe "#start_fighting method" do
it "expect cow_boy start_fight" do
expect(cow_boy).to receive(:fighting?)
expect(cow_boy).to receive(:draw_the_gun)
expect(cow_boy).to receive(:be_shot)
expect(cow_boy).to receive(:continue?)
subject.start_fighting
end
end
end
这是会成功通过的测试,因为我比较懒一点直接写在初始化里面 请见谅~
然後我们在测试中期待这个物件会接收到这些方法~
但如果我们不给予这些方法的话,这个方法是会没办法成功运作的喔!
因为我们把 double
物件放进去 Bar
类别生成实体,进而执行他的 start_fighting
这个方法,其中有使用到的方法都会先去参考 double
物件有没有才继续向下执行!
可以看看如果我们只写了 double
物件的话:
RSpec.describe Bar do
let(:cow_boy) { double("Gene Autry") }
subject { described_class.new(cow_boy) }
describe "#start_fighting method" do
it "expect cow_boy start_fight" do
subject.start_fighting
end
end
end
再看看执行的 Output !
程序的执行在 if cow_boy.fighting?
中断了,内容也有提到我们的 double
物件没有接受到这个方法~
所以我们可以用最上面介绍的三种写法来填入方法,使我们可以专注在测试 Bar
的方法,而不受到 cow_boy
干扰!
而如果想要指定接收次数的话,也可以加入计数的方法喔!
毕竟某些时候你会想要限制有些方法只执行一次,或是最多两次等等的条件~
describe "#start_fighting method" do
it "expect cow_boy start_fight" do
expect(cow_boy).to receive(:fighting?)
expect(cow_boy).to receive(:draw_the_gun)
expect(cow_boy).to receive(:be_shot).twice
#expect(cow_boy).to receive(:be_shot).exactly(1).times
#expect(cow_boy).to receive(:be_shot).at_most(1).times
expect(cow_boy).to receive(:continue?)
subject.start_fighting
end
end
上面有注解的部分,都是可以用这样的写法!
现在我们加上了 twice
就是希望这个方法能被执行两次,那我们先来看看 Output
这边的意思就是,我们期待会收到两次的执行,但其实程序码只有一次!
那我们先改写要测的的方法,让牛仔中枪两次吧
def start_fighting
if cow_boy.fighting?
cow_boy.draw_the_gun
cow_boy.be_shot
cow_boy.be_shot
cow_boy.continue?
end
end
这样就能成功通过测试啦~但其实你不写任何次数的话也是会通过啦!
但既然是写测试,方法会执行几次,或是接收什麽样的参数,都是需要考量和注意的!
今天介绍了使用 double
的情境,以及为什麽要使用它~
就是希望可以让我们的工作更顺利,不要在一些奇怪的事情上烦恼,有时候被其他的物件干扰导致测试写不出来,真的是超级痛苦的!
明天会稍微介绍一下今天有提到的 allow method
然後进入 instance double
的世界!
这是一个更真实更好用的东西!
那麽今天主要要用RxJava来结合retrofit做Post的部分,与上次用Retrofit的cal...
本篇大纲:Domain & Range 输入域与输出域、Interpolate 插补值、c...
Operators 操作符是Combine 中非常重要的一部分,通过各式各样的操作符,可以将原来各自...
Hi 大家今天要跟大家介绍 DB 样板,针对 MySQL 服务。 我们主要的服务都是基本上都是 LA...
现在要来处理上一篇文章的红框部分,输入N个np.arange让他跑for loop。今天在网路上看了...