我们在软件过程中,经常发现有些情况下很难进行测试。例如下层依赖返回的错误码过于单一,不能清晰的辨别具体影响了哪里,导致上层基本无法定位问题;又例如系统参数不可重新热加载,每个测试用例都必须进行进程的重启,导致测试时间灾难级拉长;再例如依赖模块故障,导致测试不能自如的进行等等。这些都让 QA 感到代码的测试很难进行下去。这里我们简述软件可测性相关的一些思想。

一、可测性的定义

可测性 (Testability) 的概念源于上个世纪 70 年代硬件测试领域,对硬件电路的可测性提出了度量的方法,并形成了可测性分析研究分支。到上世纪 90 年代,逐渐把硬件的可测试分析研究应用到软件上。然而,软件可测性并没有统一的定义,维基百科给出的定义是:软件可测性是指在一个给定的测试环境下,软件构件(例如,软件系统,软件模块,需求或设计文档)对测试的支持的度量。 简单地说,软件的可测性就是一个计算机程序能够被测试的难易程度。如果软件可测性高,那么测试从系统中找到缺陷就越容易;如果软件的可测性低,可能会造成测试工作的增加,在一些极端的情形下,缺乏可测性可能会使部分甚至全部的测试无法进行。

二、可测性因素

软件可测性主要和代码的可观察性和可控制性这两点因素有关。其中可控制性表示是否可以方便有效地将待测模块状态控制到如测试条件要求,一些具体的要求包括:

  1. 功能点测试可达。即所有可能的输出都产生于某种输入的组合,并且通过某种输入组合,所有的代码都可能被执行;
  2. 测试工程师可直接通过输入控制软件的中间或者最终状态及变量;
  3. 输入和输出结构简单,格式保持一致且有结构;
  4. 能够便利地对测试进行说明、自动化和再生;
  5. 接口和模块功能单一,限制参数个数和取值范围,便于遍历测试;
  6. 业务流程和场景易控制;
  7. 软件分层,功能性代码至于底层,流程性代码至于高层;
  8. 系统可配置,进行模型化、参数化设计;
  9. 单元具有可隔离性能够独立被测试 每项测试是独立的,测试的顺序改变不影响测试结果。

可观察性表示是否可以方便有效地观察测试结果,既包括最后结果也包括中间结果,一些具体要求包括:

  1. 每组输入有对应的唯一的输出;
  2. 系统中间变量和最终输出可以获取到,或在运行中可查询,程序运行历史记录完善;
  3. 所有影响输出的因素都可见,底层模块需要提供测试接口;
  4. 错误输出要做到定位详细、容易识别;
  5. 系统异常可探测,通过自测机制自动侦测内部错误,自动报告内部错误;
  6. 日志规范,分级合理;
  7. 系统关键统计数值需要提供查询接口;

三、如何提高可测性

那如何提高代码的可测性呢?我们可以通过测试驱动开发(TDD)相关方式进行尝试, 但由于发展时间不长,更对开发人员的传统思维发出挑战,因此实践起来需要克服巨大困难。更实用的一些建议包括采用成熟的框架开发、标准的协议、使用设计模式等等。一般来说可测试性很好的代码必然是一个强内聚、弱耦合、接口明确、意图明晰的软件,而不具可测试性的软件往往具有过强的耦合和混乱的逻辑。

内聚是从功能角度来度量模块内的联系,它描述的是模块内的功能联系,一个好的内聚模块应当恰好做一件事。内聚从弱到强分为:偶然内聚;逻辑内聚;时间内聚;通信内聚;顺序内聚;功能内聚;

  1. 偶然内聚: 指一个模块内的各处理元素之间没有任何联系。
  2. 逻辑内聚: 指模块内执行几个逻辑上相似的功能,通过参数确定该模块完成哪一个功能。
  3. 时间内聚: 把需要同时执行的动作组合在一起形成的模块为时间内聚模块。
  4. 通信内聚: 指模块内所有处理元素都在同一个数据结构上操作(有时称之为信息内聚),或者指各处理使用相同的输入数据或者产生相同的输出数据。
  5. 顺序内聚: 指一个模块中各个处理元素都密切相关于同一功能且必须顺序执行,前一功能元素输出就是下一功能元素的输入。
  6. 功能内聚: 这是最强的内聚,指模块内所有元素共同完成一个功能,缺一不可。与其他模块的耦合是最弱的。

耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。耦合从弱到强分为: 无直接耦合;数据耦合;标记耦合;控制耦合;公共耦合;内容耦合;

  1. 无直接耦合: 指两个模块之间没有直接关系,它们分别从属于不同模块的控制和调用,它们之间不传递任何信息。
  2. 数据耦合: 指两个模块之间有调用关系,传递的是简单的数据值,相当于高级语言的值传递;
  3. 标记耦合: 指两个模块之间传递的是数据结构,如高级语言中的数组名、记录名、文件名等这些名字即标记,其实传递的是这个数据结构的地址;
  4. 控制耦合: 指一个模块调用另一个模块时,传递的是控制变量(如开关、标志等),被调模块通过该控制变量的值有选择地执行块内某一功能;
  5. 公共耦合: 指通过一个公共数据环境相互作用的那些模块间的耦合。公共耦合的复杂程序随耦合模块的个数增加而增加。
  6. 内容耦合: 这是最高程度的耦合,也是最差的耦合。当一个模块直接使用另一个模块的内部数据,或通过非正常入口而转入另一个模块内部。

为了削减计算机程序的耦合问题,通常使用依赖注入的方式实现解耦。依赖注入是不在类中实例化其他依赖的类,而是先把依赖的类实例化了,然后以参数的方式传入构造函数中,这样当类的数量变多,依赖关系变得复杂后,其他类只需要实现必须的接口,就可以方便的通过修改配置进行替换,实现解耦。

四、可测性度量

可测性是否可以度量呢?有没有工具可以帮助我们做可测性分析呢?testability-explorer 是 Google 的敏捷教练 Miško Hevery 编写的用于检测 Java 代码可测试性的工具。该工具对当前大多数主流 java 开源项目进行的测试结果,有需求的小伙伴可以亲自试下。 另外,Sonar 是一个用于代码质量管理的开源平台,用于管理源代码的质量 通过插件形式,可以支持包括 java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy 等等二十几种编程语言的代码质量管理与检测, Sonar 可以从七个维度检测代码质量,开发人员需要处理前 5 种代码质量问题:

  1. 不遵循代码标准 :sonar 可以通过 PMD,CheckStyle,Findbugs 等等代码规则检测工具规范代码编写
  2. 潜在的缺陷 :sonar 可以通过 PMD,CheckStyle,Findbugs 等等代码规则检测工具检测出潜在的缺陷
  3. 糟糕的复杂度分布 :文件、类、方法等,如果复杂度过高将难以改变,这会使得开发人员难以理解它们,且如果没有自动化的单元测试,对于程序中的任何组件的改变都将可能导致需要全面的回归测试
  4. 重复 :显然程序中包含大量复制粘贴的代码是质量低下的,sonar 可以展示源码中重复严重的地方
  5. 注释不足或者过多 : 没有注释将使代码可读性变差,特别是当不可避免地出现人员变动时,程序的可读性将大幅下降,而过多的注释又会使得开发人员将精力过多地花费在阅读注释上,亦违背初衷
  6. 缺乏单元测试 : sonar 可以很方便地统计并展示单元测试覆盖率
  7. 糟糕的设计 : 通过 sonar 可以找出循环,展示包与包、类与类之间的相互依赖关系, 检测耦合。


↙↙↙阅读原文可查看相关链接,并与作者交流