[Day 22] 谈 test double 的五种类型

昨天我们讲了针对 removeTag() 的单元测试

不过,如果我们考虑到针对 updateUsersTags() 的单元测试,我们会发现到一个问题。就是,这个函数他在设计时,就有 filter 这个传入函数的参数。某种程度上来説,这个函数先天就必须和其他的元件互动。

这样的情境下,我们还能做所谓的单元测试吗?

这边,我们就要介绍到一个新的观念:测试假人(test double)了

什麽是 test double

刚刚我们提到,有的元件设计上就会需要和其他元件互动的问题。

遇到这个情境时,我们通常会想办法设计一个假的元件,这个假元件是专门用在测试环境下,避免在测试阶段需要和真实环境的元件进行互动。

在很多情境下会非常需要这类假元件,比方说我们有个和购物相关的逻辑,我们自然不会希望每次运作自动测试时,都会跑到正式的环境下尝试去成立订单,这样的话会导致很多维护上的问题。

这类用来协助测试环境的假元件,我们统称为 test double,对应英文的测试用假人

往下细分,test double 可以分为五类

  • dummy
  • fake
  • stubs
  • spy
  • mock

下面我们花一点篇幅,来说明这五类的不同

dummy

dummy 物件,作为一个假物件,是 不做任何事情 的假物件。

通常来说这个物件只是用来放在参数列,让函数可以继续执行下去的。

举例来说,如果我们当初的 updateUsersTags(),不是写成

fun updateUsersTags(users: List<User>, tags: List<Tag>, filter: List<Tag>.()->List<Tag> = {this}) {  
    transaction {  
 users.forEach {  
 it.tags = SizedCollection(tags.filter())  
        }  
 }}

而是写成

fun updateUsersTags(users: List<User>, tags: List<Tag>, filter: List<Tag>.()->List<Tag>) {  
    transaction {  
 users.forEach {  
 it.tags = SizedCollection(tags.filter())  
        }  
 }}

那麽要让他能继续运作,就会需要 filter 里面有值。

这时我们可能就会宣告一个

val filterDummy: List<Tag>.()->List<Tag> = {this}
updateUsersTags(users, tags, filterDummy)

让程序可以继续下去。

fake

和 dummy 对应,反过来实作非常多功能的一种 test double,就是 fake 了。

fake 作为测试假体,是几乎满足正常环境的实作的。不过通常会省略一部分东西,导致适合测试环境,但是不适合正式环境。

比方说,很多後端网站在自动测试时,会选用 SQLite 来进行测试时的资料库替代品。又或者我们之前的范例,所使用的 H2 in memory database,虽然里面实作了资料库的 CRUD 功能,让我们可以顺利测试,但是 in memory 的资料库毕竟不太适合用在正式环境,所以正式环境我们切换成 MySQL。

这边的 H2 in memory database,就是一种 fake。

stub

stub 这个字,在英文里对应是树桩的意思。

顾名思义,这个东西就像是树桩一样,他没有自己的逻辑判断,只是在被敲打(呼叫)的时候,会传出固定的声音(回传) 一样。

举例,我们如果希望测试在成立订单时,如果订单建立成功,我们的程序是否能正确地往下进行回传,那我们可能就会建立一个 stub 物件,每次呼叫都固定回传订单建立成功的资讯。

spy

spy 这个英文字大家可能很熟悉,就是间谍的意思。

这类 test double 不仅仅会像是 stub 一样,每次被呼叫都回传固定讯息,而且会纪录下被呼叫的次数和被呼叫方式,像是一个间谍一样。

举前面的例子来说,如果我希望测试的项目不仅仅是订单建立成功时程序是否能正确回传,我还希望确认他尝试建立订单时所传输的资讯是正确的,这时我可能就需要建立 spy 物件,来协助我纪录这些资讯了。

mock

最後,我们提到 mock,这个字的意思是模仿,也是很多人在测试中常用的 test double。

和 stub 或 spy 不一样,mock 物件是在一开始撰写的时候,就需要设定预期被呼叫的次数与方式,如果测试内运行之後,呼叫次数或方式不符合的话,会抛出错误。

举例来说,如果我们一开始就设定好,订单要建立成功,需要呼叫元件一次,包含参数有哪些。那麽我们就可以用 mock 的方式来设定。这段测试如果没有呼叫到 mock 物件时,这个物件会抛出错误,让我们知道虽然我们程序其他的地方没有问题,但是在呼叫元件的逻辑上是有问题的。

今天的观念部分比较多,先让各位读者消化一下,我们明天见!


<<:  【Day27】this - 简易呼叫(Simple Call)

>>:  Day 12 Classify images with the Custom Vision service

Day 07 : 资料视觉化 Matplotlib

昨天介绍的资料分析後,相信大家对於资料分析都能轻松上手。把特如果要把一堆数据和资料给你的老板和顾客看...

NIST 对 ICT 供应链的常见风险

-ICT SCRM 支柱和可见性(来源:NIST SP 800-161) 仅当购买正版产品时,生命...

GitHub Action 实作持续交付 - 部署至 Azure App Service

可以 ASP.NET Core 网站部署的环境相当多,包含 IIS, Nginx, App serv...

21 | WordPress 短代码区块 Shortcode Block

短代码是可让你花费少许心力即可执行精巧工作的 WordPress 特定程序码。你可以使用短代码内嵌档...

28/AWS SSA面试经验分享(上)

虽然现在欧洲还在work from home的期间,AWS ML Specialist Soluti...