实用指南:【JUnit实战3_10】第六章:关于测试的质量(上)

JUnit in Action, Third Edition

《JUnit in Action》全新第3版封面截图

写在前面
如果说第五章是测试相关理论的开胃菜,那么第六章就称得上是本书第二大模块的速成心法了。这一章广泛探讨了影响测试质量高低的因素,进一步列举了很多编写可测试代码(testable code)的基本原则,学完本章我才对 OOP 中的多态有了更新的认识,之前工作中大都停留在表面。由于篇幅较长,第六章笔记拟分为上、下两个部分进行梳理,本篇为第六章的上篇,重点介绍测试覆盖率的相关概念、应用场景及常见工具的使用。下篇会在此基础上介绍 TDDBDD 的相关概念,并对变异测试(mutation testing)作进一步延伸。

第六章 测试质量(上)

本章概要

  • 测试覆盖率的度量方法
  • 可测试代码的写法
  • 考察测试驱动开发及行为驱动开发
  • 变异测试入门
  • 开发周期中的测试实践

本章较为深入地剖析了测试代码的质量问题,以及评价测试质量高低的相关指标;然后从实战的角度介绍了测试覆盖率的概念和对应工具插件的用法(JaCoCo)。接着探讨了编写可测试代码的几条重要准则(偏理论)。

由于测试覆盖率指标存在缺陷,作者又进一步介绍了其他提高测试质量的手段,包括测试驱动开发、行为驱动开发的基本流程,以及变异测试的相关内容(已超纲,未展开)。

最后,遵循持续集成的基本原则,介绍了项目开发的完整生命周期(四大阶段),并以此为基础介绍了何时应当执行何种类型测试的理想模式,然后梳理总结了 JUnit 的最佳实践——持续回归测试。

6.1 测试覆盖率的概念

单元测试的目的主要是增强开发者修改和重构代码的信心,关键在于这些测试代码能否及时响应代码的变更,确保改动的内容不会破坏原有的功能。换言之,开发者需要明确知晓这些单元测试代码在运行时到底执行了哪些代码。

测试覆盖率的度量方式并不唯一,通常包括两类:

  • 在测试套件执行期间,计算被调用的程序方法的占比,或者被调用代码行在总代码行中的百分比。
  • 追踪测试代码最终调用了哪些方法并给出统计结果。

本节还回答了一个十分重要的问题:测试质量仅与测试覆盖率相关,但不存在必然的因果关系——高代码覆盖率并不能确保测试的高质量。

因此,测试覆盖率是一个存在争议的度量指标。优秀的开发者应该透过现象看本质,参考它,但不完全依赖它。

6.2 黑白盒测试对测试覆盖率的影响

黑盒测试的覆盖率相对较低,原因如下:

Fig6.1

由于白盒测试可以深入功能模块的实现细节,其测试覆盖率理论上可以达到 100%:

Fig6.2

覆盖率偏低,要么测试数量不足,要么测试用例本身存在冗余,无法测到真正要测的情况。不管属于哪种,都需要更多的额外分析工作,不能一概而论。

6.3 计算测试覆盖率的工具

6.3.1 IDEA 内置工具

IDEA 内置了包含测试覆盖率的功能:

Fig6.3

实测结果:

Fig6.4

并且能生成简易的测试报告:

Fig6.8

6.3.2 第三方 Maven 插件 JaCoCo

想要获取更精确的测试覆盖率指标,可以借助第三方插件 JaCoCohttps://www.jacoco.org/jacoco/)。

先配置 Maven 插件(原书版本太久无法运行,更新到最新版 v0.8.14 后正常):

<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.14</version>
  <executions>
    <execution>
      <goals>
      <goal>prepare-agent</goal>
      </goals>
    </execution>
    <execution>
    <id>report</id>
    <phase>test</phase>
      <goals>
      <goal>report</goal>
      </goals>
    </execution>
    <execution>
    <id>jacoco-check</id>
    <phase>test</phase>
      <goals>
      <goal>check</goal>
      </goals>
      <configuration>
        <rules>
          <rule>
          <element>PACKAGE</element>
            <limits>
              <limit>
              <counter>LINE</counter>
              <value>COVEREDRATIO</value>
              </limit>
            </limits>
          </rule>
        </rules>
      </configuration>
    </execution>
  </executions>
</plugin>

运行命令 mvn test 后,会在指定的 target/site/jacoco/ 下生成多种格式的测试报告:

Fig6.5

用浏览器打开 index.html 看到更详细的测试结果:

Fig6.6

甚至能查看目标模块每一行的覆盖情况:

Fig6.7

6.4 编写可测试代码的基本原则

原则一:公共 API 即契约

首先是深化理解:公共 API 接口就是某种意义上的契约,变更需趁早,且务须慎重(NASA 火星探测器的血泪教训)。

原则二:减少依赖

不要对依赖直接实例化,而要改为依赖注入,以方便后续测试:

// before
class Vehicle {
Driver d = new Driver();
boolean hasDriver = true;
private void setHasDriver(boolean hasDriver) {
this.hasDriver = hasDriver;
}
}
// after
class Vehicle {
Driver d;
boolean hasDriver = true;
Vehicle(Driver d) {
this.d = d;
}
private void setHasDriver(boolean hasDriver) {
this.hasDriver = hasDriver;
}
}

原则三:构造函数要尽量简单

每个测试用例需要走到的基本步骤:

  1. 实例化待测试的类;
  2. 给这个类配置相应的状态;
  3. 执行必要的操作;
  4. 断言最终的状态。

因此,按照单一职责的思想,构造函数尽量不要混入太多无关逻辑:

// before
class Car {
private int maxSpeed;
Car() {
this.maxSpeed = 180;
}
}
// after
class Car {
private int maxSpeed;
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
}

(上篇完)

posted on 2025-11-25 08:59  ljbguanli  阅读(0)  评论(0)    收藏  举报