lsekfe 发表于 2022-4-1 11:03:45

单元测试框架和覆盖率统计原理简析(一)

一、背景介绍
  最近部门在推进质量标准化,通过标准化研发、交付、部署、运维等过程,减少缺陷率和返工率,提高整体的工作效率。而单元测试又是软件研发过程中的重要一环,此文可以帮助理解单元测试插件的运行过程,了解 mock 框架以及平台覆盖率统计相关的原理,从而更好更快地编写单元测试。
  二、 单元测试与敏捷开发
  在常规的测试环节中,可以较为笼统地作以下分类:
  ·单元测试:快速地检查一个类或极小范围内的功能
  · 冒烟测试:快速检查系统核心功能是否有明确缺陷
  · 集成测试:检查整个应用或系统的功能
  · 回归测试:检查变更代码是否破坏旧的功能
  · 黑盒测试:将整个系统看成一个黑盒进行不特定测试
  · 白盒测试:按照编码或运行细节,设计特定的测试过程
  完全没有实施过单元测试的团队,推进过程可以按照确定覆盖率基线、覆盖率摸底、持续补充用例、持续提升单元测试质量和覆盖率几个环节。最终的目的是希望研发人员从被动编写到主动编写,不断提升代码可测性,将低级缺陷扼杀在集成测试之前。

 从不写单元测试、不会写单元测试,到写单元测试、写有效的单元测试,可以从4个阶段来推进。

 三、Maven & JUnit 的关系
  1.Maven 的简介
  名词解释
  Maven 中有两个核心概念,phase 和 goal,先通过 IDEA 中的 Maven 面板,来直观地感受一下 phase 和 goal 的区别:
  1)goals
  goals 是属于具体 maven 插件的一个任务,可以完成一件具体的事情。例如在 Spring Boot 的官方插件中,就提供了 run 这个任务,帮助我们直接通过 maven 命令运行我们的 Spring Boot 应用。
  具体实践中,会将 goal 绑定在某个 phase 运行时执行。

2)lifecycle 和 phase
  phase 是 Maven 定义的一套通用编译过程,例如 compile、deploy,phase 本身并没有具体行为,需要依赖相关插件绑定任务。

可以通过在 pom 文件中的插件声明,指定某个 goal 运行的时机(phase)。
 <executions>
     <execution>
        <goals>
        <goal>repackage</goal>
        </goals>
        <phase>package</phase>
     </execution>
  </executions>三套生命周期
  Maven 的整体架构采用了 core + plugin 的方式,core 可以当成一个 Launcher,启动过程中会有一些切换主类的过程,Tomcat、Spring Boot、Pandora Boot 都有类似的设计。
  Maven 中定义了三套生命周期:clean、default、site,每个生命周期会包含一些阶段(phase)。三套生命周期相互独立,执行某个 phase 时,按序执行且顺序靠前 phase 先执行,直到指定的 phase 运行结束,之后的 phase 不会再执行。
  1)clean 生命周期
  目的是做一些构建文件的清理工作。
pre-clean..............执行清理前的工作
  clean..................清理上一次构建生成的所有文件
  post-clean.............执行清理后的工作 2)default 生命周期
  包含了最常用的 phase,定义了构建项目时的核心过程。
validate
  initialize
  generate-sources
  process-sources
  generate-resources
  process-resources......复制和处理资源文件到target目录,准备打包
  compile................编译项目的源代码
  process-classes
  generate-test-sources
  process-test-sources
  generate-test-resources
  process-test-resources
  test-compile...........编译测试源代码
  process-test-classes
  test...................运行测试代码
  prepare-package
  package................打包成jar或者war或者其他格式的分发包
  pre-integration-test
  integration-test
  post-integration-test
  verify
  install................将打好的包安装到本地仓库,供其他项目使用
  deploy.................将打好的包安装到远程仓库,供其他项目使用3)site 生命周期
pre-site
  site...................生成项目的站点文档
  post-site
  site-deploy............发布生成的站点文档
mvn test 和 mvn surefire:test
  surefire:test 是 maven-surefire-plugin 中定义的一个任务,默认绑定在 test 阶段运行
  ·当运行 mvn test命令时,先运行 test 阶段之前的 compile、test-compile 等 phase 及绑定 goal 任务;
  · 而 mvn surefire:test 则是直接运行这个任务,不会执行编译,因此需要提前手动编译好源代码和测试代码;
  Maven 会自动收集当前项目的所有模块,做依赖树和插件合并。当 pom 中未声明任何插件或者插件版本号为空,Maven 会使用默认值进行填充。
  maven 的 default 生命周期和插件版本关系的声明文件:maven-core-3.6.3.jar/META-INF/plexus/default-bindings.xml。

在收集到所有的插件信息后,会按照 phase 顺序依次执行。

 2.单元测试框架 JUnit
  JUnit 是 Java 开发测试中最常用的单元测试框架,它是由 Kent Beck (极限编程和测试驱动开发的创始人) 和 Erich Gamma 共同编写,其灵感来自于 Kent Beck 早期在 SUnit (一种针对 Smalltalk 编程语言的测试框架) 上的工作。
  JUnit 属于 xUnit测试框架家族。xUnit 家族中的测试框架通常会定义这几个运行过程: setup, exercise, verify, teardown。

JUnit3 中我们能见到一些约定的类名和方法名,这是因为早期的 JDK 并不支持注解。直到 JDK 1.5 支持注解,才使得 JUnit4 基于注解声明测试用例变成可能。
  JUnit4 采用 @Annotation 标注的方式,比 JUnit3 的通过类继承和特定方法名带来更大的灵活性,而且只有一个 jar 包非常易于集成。

 JUnit5(2016)
  JUnit5 则诞生于一个互联网新技术大爆发的时期,它的目标是作为一个测试平台,来连接测试工具、测试引擎和用例。JUnit5 的主要组件可以分为。


JUnit4 是如何被 Maven 唤起的
  前文提到,surefire 插件的会将测试任务绑定在 test 阶段,因此当运行 mvn test时会调用 surefire 插件的方法。surefire 通过 SPI 机制扫描类路径下,发现测试引擎实现类(需要实现 org.apache.maven.surefire.providerapi.SurefireProvider),从而将测试任务转嫁到具体的执行引擎 。

按测试类名称过滤
  Maven Surefire 插件无其他测试框架的依赖注入时,默认使用 JUnit3Provider 作为执行引擎,因此要求测试类命名为以下模式:
  ·**/Test*.java
  · **/*Test.java
  · **/*Tests.java
  *· */*TestCase.java
  过程中会排除所有嵌套类(包括静态成员类),也可以通过在 pom 文件中配置include和exclude规则来覆盖默认行为。
  默认引擎下扫描测试方法的规则:
  · 测试方法必须是 public, 非 static,返回类型为 void,无参的方法;
  · 测试方法必须写成testXxx形式;
  · 全局变量可以在无参的构造方法中初始化;
  · 每次执行一个测试用例前,执行一遍setUp(),用于对数据的初始化;执行完一个测试用例后,再执行tearDown(),用于销毁还原数据;
  因此当我们需要使用 JUnit4 的注解如 @Before,需要添加依赖 surefire-junit4 告诉 SurefirePlugin 优先使用 JUnit4 的 @runner 来运行。
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.16</version>
  <dependencies>
      <dependency>
        <groupId>org.apache.maven.surefire</groupId>
        <artifactId>surefire-junit4</artifactId>
        <version>2.16</version>
      </dependency>
  </dependencies>
  </plugin>
这就是为什么在本地运行 IDEA 没问题, Aone 或者命令行运行就出错的原因,因为缺少依赖 Maven 无法识别 JUnit4 注解,导致很多变量没有被 @before 注解标注的方法初始化。











页: [1]
查看完整版本: 单元测试框架和覆盖率统计原理简析(一)