软件测试:实践者方法
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.3.3 不同级别的可测试性问题

2.3.3.1 代码级别的可测试性

代码级别的可测试性,通常用于度量单元测试的难易程度。对于一段代码,如果需要依赖测试框架和Mock框架的高级特性,或奇技淫巧,才能完成测试,则意味着该代码的可测试性较差。编写具有良好可测试性的代码并非易事,违反可测试性的反模式不胜枚举。比如,无法Mock依赖的组件或服务、代码中包含未决行为逻辑、滥用可变全局变量、滥用静态方法、使用复杂的继承关系、高度耦合的代码、I/O和计算不解耦,等等。而那些随心所欲的注释、莫名其妙的链接,如果再“下点毒”,测试人员不“吐血”才怪!

除编程技能外,良心、规范是确保代码可测试性的基础。为了便于理解代码级别的可测试性,下面以“无法Mock依赖的组件或服务”为例进行说明。

上述Transaction类是经过抽象简化的一个电商系统交易类,用以记录每笔订单的交易情况,类中的execute()函数实现转账操作,将交易费用从买家转入卖家,通过Execute()函数调用WalletRpcService RPC服务完成转账操作。对此,编写如下测试代码,通过提供参数调用Execute()函数,实现上述转账服务测试。

该测试代码提供参数调用Execute()函数,但为了使得该测试能够顺利运行,需要部署WalletRpcService服务,但搭建和维护成本较高,且需要确保将构造的transaction数据发送给WalletRpcService服务之后,返回期望结果以完成不同路径覆盖。基于网络的测试执行,耗时较长,网络中断、超时以及WalletRpcService服务不可用等情况都会影响测试执行,需要用Mock实现依赖解耦,即用一个“假”服务替换“真”服务,模拟输出所需数据,以便控制测试执行路径。因此,构建如下Mock,通过继承WalletRpcService类,重写moveMoney()函数,就可以让moveMoney()返回任意想要得到的数据,而无须进行网络通信。

接下来,如果用MockWalletRpcServiceOne和MockWalletRpcServiceTwo代替代码中的WalletRpcService,就会发现WalletRpcService是在execute()函数中通过new方式创建,无法动态地对其替换,这就是典型的代码测试性问题。

为了能够有效地解决该问题,可通过依赖注入方式,对代码进行适当重构,将WalletRpcService对象的创建反转给上层逻辑,在外部创建完成之后,将其注入到Transaction类中。重构后的测试代码如下:

2.3.3.2 服务级别的可测试性

在服务级别,基于服务架构,可测试性包括接口设计文档的详细程度、接口设计的契约化程度、私有协议设计的详细程度、服务内部状态的可控制性、服务运行的可隔离性、服务扇入扇出大小、服务资源占用的可观测性、内置测试(Built-In Test,BIT)的实现程度以及服务部署、服务配置信息获取、测试数据构造、服务输出结果验证、服务后向兼容性验证、服务契约获取与聚合、内部异常模拟、外部异常模拟、服务调用链路追踪等的难易程度。

2.3.3.3 业务需求级别的可测试性

业务需求级别的可测试性可划分为人工及自动化测试的可测试性,通常包括登录过程中的图片或短信验证码、硬件U盾/USB Key、触屏应用的自动化测试设计、第三方系统的依赖与模拟、业务测试流量隔离、系统不确定性弹框、非回显结果验证、可测试性与安全性平衡、业务测试的分段执行、业务测试数据构造等典型场景。