软件的“可测试性”
- 2022-04-06 10:00:00
- 茹炳晟
- 转贴:
- 公众号
- 3370
随着云原生技术的加速普及与快速发展,软件系统的规模和复杂性不断水涨船高。与此相对应,在软件研发过程中,为测试而设计(Design for testing),为部署而设计(Design for deployment),为监控而设计(Design for monitor),为扩展而设计(Design for scale)和为失效而设计(Design for failure)正在变得越来越重要,甚至成为了衡量软件组织核心研发能力的主要标尺。
本文重点探讨 “ 为测试而设计”的理念,以软件可测试性(Testability)作为主线,为大家阐述软件可测试性的方方面面以及软件组织在这个方向上的一些最佳实践与探索。
软件可测试性对软件研发和质量保障有着至关重要的作用,是实现高质量、高效率交付的基础。可测试性差,会直接增加测试成本,让测试的结果验证变得困难,进而会让工程师不愿意做测试,或者让测试活动延迟发生,这些都违背了“持续测试,尽早以低成本发现问题”的原则。为此我们有必要对可测试性进行一次深入浅出的探讨,主要内容包含以下 5个方面。
-
可测试性的定义
-
可测试性差引发的问题
-
可测试性的三个核心观点
-
可测试性的四个维度
-
不同级别的可测试性与工程实践
01
可测试性的定义
软件的可测试性是指在一定时间和成本前提下,进行测试设计、测试执行以此来发现软件的问题,以及发现故障并隔离、定位其故障的能力特点。各种组织对可测试性有不同的定义(如图1),我认为其本质是相通的,都是在说一个软件系统能够被测试的难易程度,或者是说软件系统可以被确认的能力。
我个人比较喜欢的定义是来自James Bach的版本:“可测试性就是一个计算机程序能够被测试的容易程度”。
图1:可测试性的各种定义
02
可测试性差引发的问题
很对人会觉得可测试性似乎是个新命题,在软件测试发展的很长一段时间里,这个概念似乎并没有被广泛提及。那是因为以前的软件测试是偏粗狂式的黑盒模式,而且测试团队和开发团队是分离,测试工程师往往在研发后期才会介入,测试始终处于被动接受的状态。并且大量的测试与验证都是偏向黑盒功能,所以可测试性的矛盾并没有被凸显出来。但是在今天,随着测试左移,开发者自测,测试与开发融合以及精准测试的广泛普及,这种粗狂式的黑盒验证已经无法满足软件的质量要求。
如果继续忽视可测试性,不从源头上对可测试性予以重视,将会导致研发过程中系统不可测,或者测试成本过高的窘境。可以说,忽视可测试性就是在累积技术债务。更何况,今天大行其道的DevOps全程都离不开测试,测试成为了拉通持续集成与持续发布(CI/CD)各个阶段的“连接器”,如果可测试性不行,整个持续集成与发布的效率也会大受影响。
为了帮助大家更好地理解可测试性,这里先列举一些实际的可测试性问题作为抛砖引玉。
GUI测试层面:
-
登录场景下的图片验证码: 图片验证码虽然不影响手工测试,但是对自动化测试的可测试性就很不友好,用OCR技术识别图片验证码不够稳定,如果能够实现稳定识别反而说明验证码机制有问题。如果登录实现不了自动化就会影响很多其他的自动化测试场景。对于登录过程中的短信验证码也有类似的可测试性问题。
-
页面控件没有统一且稳定的ID标识: 如果页面控件没有统一并且稳定(不随版本发布而变化)的ID,自动化测试脚本中控件识别的稳定性就会大打折扣,虽然测试脚本可以通过组合属性、模糊识别等技术手段来提升识别的稳定性,但是测试的成本就会变高。
-
非标准控件的识别: 非标准的前端页面控件无法通过GUI自动化测试识别,这个常见于Client端的测试。
-
需要对图片格式的输出进行验证: 图片的验证缺乏有效的工具支持,即使通过像素对比方案其稳定性也很差。
-
业务流程过长难以分解: 业务流程和业务场景过长,很难拆解后进行局部的验证。
-
不可控的随机弹窗: 有些应用会有随机弹窗的功能,比如弹窗广告,或者用户满意度调查等,这类不可控的弹窗会直接影响自动化测试的可测试性。
接口测试层面:
-
接口测试缺乏详细的设计文档: 接口测试如果没有设计契约文档作为衡量测试结果的依据,就会造成测试沟通成本高,无法有效开展结果验证,开发和测试来回扯皮的尴尬窘境。即使有了文档,还必须保持文档能够及时更新,否则会造成误导。
-
构建Mock服务的成本过高: 微服务架构下,如果构建Mock服务的难度和成本过高,会直接造成不可测或者测试成本过高。
-
接口调用的结果验证困难: 接口成功调用后,判断接口行为是否符合预期的验证点难易获取。
-
接口调用不具有幂等性: 接口内部处理逻辑依赖与未决因素,比如时间、不可控输入、后台批处理job、随机变量等,破坏接口调用的幂等性。
-
接口参数设计过于复杂,暴露了很多不必要的参数: 很多内部参数不应该在接口参数上暴露出来,这些参数应该做到无感知,需要保持接口设计的简单性。
-
使用定制化的私有协议: 非标的私有化协议会提升测试的难度,通用类的工具无法直接使用。
代码层面:
私有函数的调用: 在代码级测试中,私有函数无法直接调用。 私有变量的访问: 私有变量缺乏访问手段,以至于无法进行结果验证。 函数功能的多样性: 一个函数如果颗粒度太大,同时实现了好几个功能,会大大提升测试的难度,一来这是因为功能多必然入参也多,测试的时候参数初始化难度就会变大,二来结果验证的关注点也会同时变多,容易出现更多的组合验证,严重的时候会出现组合爆炸。 代码依赖关系复杂: 被测代码中依赖了外部系统或者不可控组件,比如,需要依赖第三方服务、网络通信、数据库等。 代码可读性差: 代码使用“奇技淫巧”,造成可读性差,同时又缺乏必要的注释说明。 重复代码多: 重复代码意味着重复逻辑,如果有改动,各个重复逻辑都需要被测试到,测试成本高。 代码的圈复杂度(Cyclomatic Complexity)过高: 圈复杂度过高的代码往往测试成本很高。 设计上钩子和注入点缺失: 没有预留钩子或者注入点,后期调试和定位问题的扩展能力变差。
通用层面:
系统错误较多: 被测系统的错误如果比较多,那么就会阻碍后续测试的执行,很多隐藏的问题就没有办法及时暴露,直接影响可测试性。 无法获取软件运行时的内部信息: 测试执行过程中,有些结果的验证需要获取软件内部的信息进行比对,如果无法通过低成本的手段获取信息,测试的验证成本就会很高。 复杂测试数据的构建: 很多测试设计都依赖于特定的测试数据,如果多样性的测试数据构建比较困难,也会直接影响系统的可测试性。 无法获取系统运行时的实时配置: 无法获取实时配置就意味着无法重建测试环境用于问题的重现和定位,增加了测试的难度与不确定性。 压测场景下的性能Profiling: 很多性能问题只能在高负载场景下才能重现,但是在高负载场景下无法通过日志的方式来获取系统性能数据,因为一旦提高了日志等级,日志输出本身就会成为系统瓶颈,进而把原来的性能问题掩盖掉了。
可以看到,可测试性问题不仅出现在端到端的功能测试层面,在接口测试和代码级测试层面都有可测试性问题,而且可测试性对于自动化测试的实现成本也很关键。
类似的例子还能举出很多,比如不受控制的触发条件、由时间触发的逻辑、难易获取的条件、调用链路获取和大量外部系统依赖等等,这里限于篇幅就不再一一展开了。
03
可测试性的三个核心观点
图2:可测试性的3个核心观点
01
可测试性是设计出来的
02
提升可测试性可以节省研发成本
03
关注可测试性可以提升软件质量
04
可测试性的四个维度
图3:由James Bach提出的“实际可测试性”模型
图5:可测试性的4个维度
可控制性
- 联系人:阿道
- 联系方式: 17762006160
- 地址:青岛市黄岛区长江西路118号青铁广场18楼