软件的“可测试性”

2022-04-06 10:00:00
茹炳晟
转贴:
公众号
2457

随着云原生技术的加速普及与快速发展,软件系统的规模和复杂性不断水涨船高。与此相对应,在软件研发过程中,为测试而设计(Design for testing),为部署而设计(Design for deployment),为监控而设计(Design for monitor),为扩展而设计(Design for scale)和为失效而设计(Design for failure)正在变得越来越重要,甚至成为了衡量软件组织核心研发能力的主要标尺。


本文重点探讨 为测试而设计的理念,以软件可测试性(Testability)作为主线,为大家阐述软件可测试性的方方面面以及软件组织在这个方向上的一些最佳实践与探索。


软件可测试性对软件研发和质量保障有着至关重要的作用,是实现高质量、高效率交付的基础。可测试性差,会直接增加测试成本,让测试的结果验证变得困难,进而会让工程师不愿意做测试,或者让测试活动延迟发生,这些都违背了“持续测试,尽早以低成本发现问题”的原则。为此我们有必要对可测试性进行一次深入浅出的探讨,主要内容包含以下 5个方面


  • 可测试性的定义

  • 可测试性差引发的问题

  • 可测试性的三个核心观点

  • 可测试性的四个维度

  • 不同级别的可测试性与工程实践

 


01

可测试性的定义


软件的可测试性是指在一定时间和成本前提下,进行测试设计、测试执行以此来发现软件的问题,以及发现故障并隔离、定位其故障的能力特点。各种组织对可测试性有不同的定义(如图1),我认为其本质是相通的,都是在说一个软件系统能够被测试的难易程度,或者是说软件系统可以被确认的能力。


我个人比较喜欢的定义是来自James Bach的版本:“可测试性就是一个计算机程序能够被测试的容易程度”。


auto-definition-testability

图1:可测试性的各种定义


测试设计能力,暨创造性地设想各种可能性,并设计相应场景是每个软件测试人员的核心技能,但是如何根据测试设计构造出所需要的测试条件,如何高效执行测试,以及测试执行过程中如何对结果进行实时的观察和验证,则是可测试性需要解决的问题。

 


02

可测试性差引发的问题


很对人会觉得可测试性似乎是个新命题,在软件测试发展的很长一段时间里,这个概念似乎并没有被广泛提及。那是因为以前的软件测试是偏粗狂式的黑盒模式,而且测试团队和开发团队是分离,测试工程师往往在研发后期才会介入,测试始终处于被动接受的状态。并且大量的测试与验证都是偏向黑盒功能,所以可测试性的矛盾并没有被凸显出来。但是在今天,随着测试左移,开发者自测,测试与开发融合以及精准测试的广泛普及,这种粗狂式的黑盒验证已经无法满足软件的质量要求。


如果继续忽视可测试性,不从源头上对可测试性予以重视,将会导致研发过程中系统不可测,或者测试成本过高的窘境。可以说,忽视可测试性就是在累积技术债务。更何况,今天大行其道的DevOps全程都离不开测试,测试成为了拉通持续集成与持续发布(CI/CD)各个阶段的“连接器”,如果可测试性不行,整个持续集成与发布的效率也会大受影响。


为了帮助大家更好地理解可测试性,这里先列举一些实际的可测试性问题作为抛砖引玉。


GUI测试层面:



  • 登录场景下的图片验证码 图片验证码虽然不影响手工测试,但是对自动化测试的可测试性就很不友好,用OCR技术识别图片验证码不够稳定,如果能够实现稳定识别反而说明验证码机制有问题。如果登录实现不了自动化就会影响很多其他的自动化测试场景。对于登录过程中的短信验证码也有类似的可测试性问题。

  • 页面控件没有统一且稳定的ID标识 如果页面控件没有统一并且稳定(不随版本发布而变化)的ID,自动化测试脚本中控件识别的稳定性就会大打折扣,虽然测试脚本可以通过组合属性、模糊识别等技术手段来提升识别的稳定性,但是测试的成本就会变高。

  • 非标准控件的识别 非标准的前端页面控件无法通过GUI自动化测试识别,这个常见于Client端的测试。

  • 需要对图片格式的输出进行验证 图片的验证缺乏有效的工具支持,即使通过像素对比方案其稳定性也很差。

  • 业务流程过长难以分解 业务流程和业务场景过长,很难拆解后进行局部的验证。

  • 不可控的随机弹窗 有些应用会有随机弹窗的功能,比如弹窗广告,或者用户满意度调查等,这类不可控的弹窗会直接影响自动化测试的可测试性。


接口测试层面:



  • 接口测试缺乏详细的设计文档 接口测试如果没有设计契约文档作为衡量测试结果的依据,就会造成测试沟通成本高,无法有效开展结果验证,开发和测试来回扯皮的尴尬窘境。即使有了文档,还必须保持文档能够及时更新,否则会造成误导。

  • 构建Mock服务的成本过高 微服务架构下,如果构建Mock服务的难度和成本过高,会直接造成不可测或者测试成本过高。

  • 接口调用的结果验证困难 接口成功调用后,判断接口行为是否符合预期的验证点难易获取。

  • 接口调用不具有幂等性 接口内部处理逻辑依赖与未决因素,比如时间、不可控输入、后台批处理job、随机变量等,破坏接口调用的幂等性。

  • 接口参数设计过于复杂,暴露了很多不必要的参数 很多内部参数不应该在接口参数上暴露出来,这些参数应该做到无感知,需要保持接口设计的简单性。

  • 使用定制化的私有协议 非标的私有化协议会提升测试的难度,通用类的工具无法直接使用。

代码层面:



  • 私有函数的调用 在代码级测试中,私有函数无法直接调用。
  • 私有变量的访问 私有变量缺乏访问手段,以至于无法进行结果验证。
  • 函数功能的多样性 一个函数如果颗粒度太大,同时实现了好几个功能,会大大提升测试的难度,一来这是因为功能多必然入参也多,测试的时候参数初始化难度就会变大,二来结果验证的关注点也会同时变多,容易出现更多的组合验证,严重的时候会出现组合爆炸。
  • 代码依赖关系复杂 被测代码中依赖了外部系统或者不可控组件,比如,需要依赖第三方服务、网络通信、数据库等。
  • 代码可读性差 代码使用“奇技淫巧”,造成可读性差,同时又缺乏必要的注释说明。
  • 重复代码多 重复代码意味着重复逻辑,如果有改动,各个重复逻辑都需要被测试到,测试成本高。
  • 代码的圈复杂度(Cyclomatic Complexity)过高 圈复杂度过高的代码往往测试成本很高。
  • 设计上钩子和注入点缺失 没有预留钩子或者注入点,后期调试和定位问题的扩展能力变差。


通用层面:



  • 系统错误较多 被测系统的错误如果比较多,那么就会阻碍后续测试的执行,很多隐藏的问题就没有办法及时暴露,直接影响可测试性。
  • 无法获取软件运行时的内部信息 测试执行过程中,有些结果的验证需要获取软件内部的信息进行比对,如果无法通过低成本的手段获取信息,测试的验证成本就会很高。
  • 复杂测试数据的构建 很多测试设计都依赖于特定的测试数据,如果多样性的测试数据构建比较困难,也会直接影响系统的可测试性。
  • 无法获取系统运行时的实时配置 无法获取实时配置就意味着无法重建测试环境用于问题的重现和定位,增加了测试的难度与不确定性。
  • 压测场景下的性能Profiling 很多性能问题只能在高负载场景下才能重现,但是在高负载场景下无法通过日志的方式来获取系统性能数据,因为一旦提高了日志等级,日志输出本身就会成为系统瓶颈,进而把原来的性能问题掩盖掉了。

 

可以看到,可测试性问题不仅出现在端到端的功能测试层面,在接口测试和代码级测试层面都有可测试性问题,而且可测试性对于自动化测试的实现成本也很关键。


类似的例子还能举出很多,比如不受控制的触发条件、由时间触发的逻辑、难易获取的条件、调用链路获取和大量外部系统依赖等等,这里限于篇幅就不再一一展开了。



03

可测试性的三个核心观点


在正式讨论可测试性的技术性细节之前,很有必要先把可测试性的核心观点先和大家对齐。我认为可测试性有三个核心观点(图2)。

auto-3-core-ideas-of-testability

图2:可测试性的3个核心观点



01

可测试性是设计出来的





毋容置疑,可测试性不是与生俱来的,而是被设计和实现出来的。可测试性必须被明确地设计,并且正式纳入需求管理的范畴。在研发团队内,测试架构师应该牵头推动可测试性的建设,并和软件架构师、开发工程师和测试工程师达成一致。测试工程师和测试架构师应该是可测试性需求的提出者,并且负责可测试性方案的评估和确认。在研发过程中,可测试性的评估要尽早开始,一般始于需求分析和设计阶段,并贯穿研发全流程,所以可测试性不再是测试工程师的责任,而是整个研发团队的职责。



02

提升可测试性可以节省研发成本





良好的可测试性意味的测试的时间成本和技术成本都会降低,同时还能提升自动化测试的可靠性与稳定性,降低自动化测试的成本。今天在可测试性上的前期投资,会带来后续测试成本的大幅度降低。今天多花的一块钱可以为将来节省十块钱,这点再次证明了“很多时候选择比努力更重要”这一观点。



03

关注可测试性可以提升软件质量





可测试性好的软件必然拥有高内聚、低耦合、接口定义明确、行为意图清晰的设计。在准备写新代码时,要问自己一些问题:“我将如何测试我的代码?我将如何在尽量不考虑运行环境因素的前提下编写自动化测试用例来验证代码的正确性?”如果你无法回答好这些问题,那么请重新设计你的接口和代码。当你在开发软件时,时常问自己“我将如何验证软件的行为是否符合预期”,并且愿意为了达成这个目标对软件进行良好的设计,作为回报,你将得到一个具有良好结构的系统。

最后,我想说的是 质量是奢侈品,可测试性更是奢侈品中的奢侈品。要让研发团队重视可测试性是件很难的事情。究其根本原因是因为研发团队“不够痛”。

长久以来,测试和开发一直是分开的两个团队,开发工程师往往更关注功能的实现,充其量会去关注一些类似性能、安全和兼容性相关的非功能需求,对于可测试性基本是没有任何优先级的,因为测试工作并不是由开发工程师自己完成的,可测试性的价值开发工程师根本就感受不到。而测试工程师虽然饱受可测试性的各种折磨,可是又苦于在软件研发生命周期的下游,对此也无能为力,因为很多可测试性需求是需要在设计阶段就考虑并实现的,到了最后的测试阶段很多事情已经为时已晚。

很多时候,你不想改是因为你不痛,你不愿改是因为你不够痛,只有真正痛过才知道改的价值。所以应该让让开发工程师自己承担测试工作,这样开发工程师会切身的感受到可测试性的重要性与价值,进而在设计与实现阶段赋予系统更优秀的可测试性,由此而来的良性循环能让系统整体可测性始终处于较高水平。这其实也是开发者自测能够带了的一个好处。关于开发者自测的话题,可以关注我之前在InfoQ上写的一篇热文“开发要不要自己做测试?怎么做?”。

 


04

可测试性的四个维度


可测试性的分类方法很多不同的版本。比如由James Bach提出的“实际可测试性”模型(Heuristics of Software Testability)(图3),由Microsoft提出的SOCK可测试性模型(图4),由Siemens提出的“可测试性设计检查表”模型等。


auto-practical-testability

图3:由James Bach提出的“实际可测试性”模型

(Heuristics of Software Testability)


auto-sock-testability-model

图4:由Microsoft提出的SOCK可测试性模型


虽然各种不用分类方法的切入点不尽相同,但是其本质确是相通的。在这些模型的基础上,我做了一些归纳和总结,将其定义成可控制性、可观测性、可追踪性与可理解性四个维度(图5)。下面我们依次展开讨论。


auto-4-dimensions

图5:可测试性的4个维度



可控制性



可控制性是指能否容易地控制程序的行为、输入和输出,是否可以将被测系统的状态控制到测试条件的要求。一般来讲,可控制性好的系统一定更容易被测试,也更容易实现自动化测试。可控制性一般体现在以下各个方面:
发表评论
评论通过审核后显示。
联系我们
  • 联系人:阿道
  • 联系方式: 17762006160
  • 地址:青岛市黄岛区长江西路118号青铁广场18楼