《有效的单元测试》一3.1 测试替身的威力

《有效的单元测试》一3.1 测试替身的威力

大家好,又见面了,我是全栈君。

本节书摘来自华章出版社《有效的单元测试》一书中的第3章,第3.1节,作者 (芬)Lasse Koskela,更多章节内容可以访问云栖社区“华章计算机”公众号查看

3.1 测试替身的威力

甘地(Mahatma Gandhi)说过:“改变世界从自身做起”。(Be the change you want to see in the world.)测试替身响应了甘地的召唤,成为你在代码中希望见到的变化。牵强附会?容我慢慢道来。
代码是一个大集合。它是指代其他代码的代码网络。每一块都有预定义的行为——作为程序员的你定义了那些行为。某些行为是原子的,包含在单个类或方法中。某些行为意味着不同代码块之间的交互。
为了时不时地验证一段代码的行为符合你的期望,最好的选择是替换其周围的代码,使你获得对环境的完整控制,从而在其中测试你的代码。你有效地将被测代码与其协作者隔离开,以便进行测试,如图3.1所示。

image

这是引入测试替身的最根本原因——将被测代码与周围隔离开。此外,如本章开头所述,还存在许多其他原因。我们认为“仅供测试”的工具是为了:
隔离被测代码
加速执行测试
使执行变得确定
模拟特殊情况
访问隐藏信息
存在多种类型的测试替身可供实现这些效果。多数效果可以用一种测试替身实现,而有些则只匹配于某种特定类型。3.2节会再次讨论这些问题。现在,我想对列出的理由建立共识——在第一时间获得测试替身的理由,以及使用它们的目的。

3.1.1 隔离被测代码

讨论在面向对象编程语言的上下文中隔离被测代码时,我们的世界包含两种东西:
被测代码
与被测代码交互的代码
当我们说要“隔离被测代码”时,意味着将需要测试的代码与所有其他代码隔离开来。如此一来,我们不仅使测试更加有针对性和容易理解,还更容易建立测试。实际上,“所有其他代码”包括了从被测代码中调用的代码。代码清单3.1通过一个简单的例子来展示。
image

如你所见,这个例子包含了汽车(Car)、汽车引擎(Engine)和由一系列方向(Directions)组成的路径(Route)。假设现在你想要测试汽车。我们总共有四个类,其中一个是被测代码(Car),两个是协作者(Engine和Route)。为什么Directions不是协作者?某种意义上,Car引用和调用了Directions上的方法。但是还有另一个角度去观察这个场景。我们看看图3.2能否帮助澄清这个观点。

image

如果我们从Car的方法中引用的类来关注高一级的抽象层次,并站在Car的角度,我们看到的会是Car通过Route来获取和访问Directions(如图3.2)。因此,用测试替身替换Engine和Route,即可将Car与其所有的协作者都隔离开。由于我们用伪实现替换了Route,因此完全控制了向Car提供的各种Directions。
既然你明白了基本原则,即如何通过一些测试替身进行替换从而获得控制,我们再来看看用它们还能做哪些好玩儿的事情。

3.1.2 加速执行测试

替换掉真实协作者会带来一个愉悦的副作用,那就是测试替身的实现经常比真实事物执行得要快。有时,测试替身的速度不只是副作用,而是使用测试替身的主要原因。
考虑图3.2中的驾驶例子。假设初始化Route要涉及加权图搜索算法,以便找出汽车(Car)当前位置与目的地之间的最短路径。由于今日街道和高速公路网络的复杂性,计算需要花一点时间。尽管折腾一次算法可能还比较快,但即使小小的延迟也会积少成多。如果每个测试都初始化一次Route,你可能会在这个算法上消耗好几秒甚至几分钟的CPU周期——当开发者运行自动化测试来获得快速反馈时,几分钟就等于永远。
放置一个测试替身,令它总是返回预先计算好的通往终点的路径,这样就会避免不必要的等待,而且测试运行得更快了。太棒了。但有些地方还是需要那些缓慢的Route算法——在单独有针对性的测试中——但你不希望到处都运行缓慢的算法。
尽管速度总是一件好事,但它不总是最重要的事情。毕竟,如果方向开错了,再快的车也没用。

3.1.3 使执行变得确定

我曾听过著名励志演讲家Tony Robbins讲到过惊喜,尽管我们都说自己喜欢惊喜,但我们只喜欢那些自己想要的惊喜。没错,对于软件也一样,特别是当谈到测试代码时。
测试就是指定行为,并验证行为符合规范。只要代码具有完全确定性,并且其逻辑不包含一丝随机性,这就是简单而直接的。其实,为了使代码(和测试)具有确定性,你就需要能够针对同样的代码重复地运行测试,并总是得到相同的结果。
很多时候,你的生产代码需要包含随机性因素,或者其他因素造成重复执行的结果不唯一。例如,如果你开发一个掷骰子的Craps游戏,你最好让骰子的结果不能预测——这就是随机。
或许不确定行为的最典型情形就是依赖于时间的行为。回到Car的例子,它向Route请求Directions,想象一下用来计算路径的算法会涉及时间,以及流量、限速等,如代码清单3.2所示。
image

这样的话,如果在不同时间执行测试,你如何确保路径算法的正确性?毕竟,算法肯定是从某个时钟获取了时间,尽管在下午3∶40或3∶50时算法可能建议走高速公路,但如果现在是下午3∶50,那么最佳结果可能突然就变成了走洲际公路,因为高速公路的晚高峰开始了。
测试替身也可以对这类不确定行为伸出援手。例如,当你的骰子变成可以作弊的测试替身,并能产出一串已知的点数序列时,Craps游戏的特定实例突然就变得容易模拟了。相类似,如果你用一个固定时刻的测试替身来替换掉系统时钟,你就更容易去描述某个日志文件的预期输出。
控制你的协作者,并在精确设置被测场景时能够消除所有变量,这是使执行变得确定的关键。说到场景,测试替身也能模拟正常情况下不会发生的情况。

3.1.4 模拟特殊情况

我们编写的大多数软件往往是简单粗暴的——至少在某种意义上,大多数代码都是确定的。因此,通过实例化合适的对象图(object graph),并将其作为参数传入被测代码,我们可以重建几乎任何的情况。当我们从“1 Infinite Loop,Cupertino,CA”出发,设置“1600 Amphitheatre Parkway,Mountain View,CA”为终点,然后说drive()(开车),那么我们可以测试代码清单3.1中Car最终应该停在正确的地方。
我们无法仅用API和产品代码的特性来创建某些情况。假设我们的Route通过互联网从Google地图来获取路线方向。若是请求方向时互联网连接不幸中断,这种情况下该如何测试Route的表现依然正常?
通过禁用计算机的网络接口进行测试,其缺点在于你无法伪造这类网络连接错误,但是若将某处替换为测试替身的话,则可以在请求连接时抛出一个异常。

3.1.5 暴露隐藏的信息

采用测试替身的最后一个(也很重要的)理由,是令我们的测试访问到无法访问的信息。特别是在Java上下文中,“暴露信息”首先想到的是允许测试能够读写其他对象的私有成员。尽管有时你决定去那样做,但这里的信息指的却是被测代码与其协作者之间的交互。
我们再用可靠的Car例子来帮助你掌握这种动态。这是从代码清单3.1中复制的Car类中的代码片段:
image

如你所见,当某人启动汽车Car的时候,汽车Car启动它的引擎Engine。你如何测试它真的发生了?你可以向测试代码暴露私有成员,并为Engine增加一个新方法用于判定引擎是否启动了。但是如果你不想那么做的话呢?要是你不想仅仅为了测试而弄乱生产代码呢?
现在你大概猜到了,答案就是测试替身。通过将Car的Engine替换为测试替身,可以向测试代码中添加仅供测试的方法,避免增加一个永远不会在生产环境中使用的isRunning()方法而弄乱你的生产代码。测试代码如代码清单3.3所示。
image

如你所见,我们的示例测试用测试替身来配置Car,启动汽车,使用测试替身来验证引擎如愿启动。强调一下,isRunning()不是Engine的方法——它是我们添加到TestEngine上的,用于揭示正常Engine所不能暴露的信息。
现在你理解了使用测试替身的最常见原因。现在该看看不同类型的测试替身了,以及它们各自所具有的优势。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/108374.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • strstr函数php,strstr 函数用法[通俗易懂]

    strstr函数php,strstr 函数用法[通俗易懂]strstrstrstr(str1,str2)函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。C语言函数函数名:strstr函数原型:1externchar*strstr(char*str1,constchar*str2);语法:1*strstr(str1,str2)str1:被查找目标 stringex…

  • 活动图学习笔记

    活动图学习笔记活动图学习笔记活动图基本概念事件流除了用文本形式来表示外,还经常用活动图来表示。为什么有了文本形式以后还要开发这种框图形式呢?这是因为利用文本形式虽然很有用,但是如果事件流逻辑复杂,则文本形式比较难阅读和理解,利用框图将比文本形式来得更加有效。活动图显示与文本事件流相同的信息。我们在业务模型中用活动框图描述业务过程的工作流。活动图的组成要素活动图的组成要素主要有:起始点和终止点、活动、迁移、决策框、

  • Unity AssetBundle

    Unity AssetBundle#AssetBundle作用原理把资源导出成一种叫做AssetBundle的文件,然后打包后可以在Unity程序运行的时候再加载回来用。AssetBundle是采取某一种压缩方式压缩成的资源文件。节省存储空间,控制游戏包的大小,实现游戏的热更新。AssetBundle文件分类AssetBundle文件可以分为两类:序列化文件(serializedfile)和资源文件(resource…

  • npm换源问题

    npm换源问题点击查看解决方案

    2022年10月23日
  • t分布与t检验的一点理解

    t分布与t检验的一点理解

  • 使用tinyxml2库解析xml

    使用tinyxml2库解析xmltinyxml2简介tinyxml2是c++编写的轻量级的xml解析器,而且是开放源代码的,在一些开源的游戏引擎中用的比较多。源码托管在github上。源码地址:https://github.com/leethomason/tinyxml2tinyxml2使用起来非常简单,下载源码后无需编译成lib文件,直接將tinyxml2.h和tinyxml2.cpp两个文件添加到你自己的工程中即可。

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号