上一章我们分享了“测试驱动开发(TDD)”。下面我们准备开始使用JUnit、AssertJ和Mockito编写单元测试。在开始编写测试之前,我们需要先把开发环境准备好。主要包括两个环节:
- 在项目中确定测试代码的存放位置。
- 在项目中引入上述三大依赖。
下面分别论述。
1. 在项目中确定测试代码的存放位置
测试代码和测试配置文件伴随产品代码的始终,同产品代码一起提交到版本库。
在Maven和Gradle项目中,测试代码应该放置在下面的目录:
src/test/java
而测试用到的资源文件(文本文件、jdbc配置文件、xml文件等等)应该放置在下面的目录:
src/test/resources
如果将测试代码和资源文件放到上述目录下,构建系统会自动找到测试代码并执行测试,不需要额外的配置。
文件布局如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cJORlGf5-1590651691142)(…/images/test-layout.jpg)]
2. 在项目中添加JUnit、AssertJ和Mockito三大依赖
目前最流行的项目构建系统是Maven和Gradle。另一个构建系统Ant已经严重落伍,所以不再讲述。
2.1 在Maven中添加JUnit、AssertJ和Mockito三大依赖
在pom.xml文件中添加下面的内容:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.16.1</version>
<scope>test</scope>
</dependency>
<!--
此处添加其他项目需要的依赖
-->
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
<!--
此处添加其他项目需要的依赖
-->
</dependencies>
通过定义dependencyManagement节,统一管理项目各个依赖项的版本和应用范围等等。这样在dependencies节中定义项目需要的依赖的时候就不再需要指定版本和应用范围。
记住这三大依赖都只用于测试,不是产品代码的一部分,因此它们的都是test而不是compile。因为默认scope就是compile,如果不将scope显式设定为test,最终产品中就可能会包含测试相关的各种类库(jar),这是你所不希望的。
上面我们采用的是JUnit5,如果你还是习惯编写JUnit4或者3的单元测试,就还需要加上这么一个依赖项:junit-vintage-engine。
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
另外,用于执行单元测试的surefire插件的版本也要用比较新的新本才能支持JUnit5:
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<!--
其他插件
-->
</plugins>
</pluginManagement>
<!--
其他内容
-->
</build>
2.2 在Gradle中添加JUnit、AssertJ和Mockito三大依赖
在build.gradle文件中写下如下代码来支持运行 Junit Platform:
test {
useJUnitPlatform()
}
同时引入测试依赖:
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.2")
testCompile("org.mockito:mockito-core:3.3.3")
testCompile("org.assertj:assertj-core:3.16.1")
//如果项目中包含JUnit4或3的测试,还需要加入下面一行
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.6.2")
}
3. 测试类命名等相关建议
有下面一些建议:
- 测试类和被测类定义在相同的包里面。
这主要是从方法可访问性的角度考虑的。我们不仅要测试被测类的public方法,有时可能还需要测试protected方法和包级私有的方法。如果测试类和被测类位于相同的包,测试类就可以访问以及测试被测类的protected和包级私有方法,否则可能没有办法测试这些方法。
- 每个工作单元对应一个测试类。
这意味着每个被测类对应多个测试类,而不是每个被测类对应一个测试类。
因为被测类往往有多个工作单元(非private方法),而每个工作单元往往需要编写一组单元测试。如果将对应一个被测类的单元测试都写在同一个测试类里面,会导致测试类很复杂,而且不够内聚,模糊了焦点。
单元测试关注的粒度是工作单元而不是类,所以应该基于工作单元来定义测试类。
如果针对一个工作单元的一组测试类的测试前准备和测试后清理工作差别比较大,也可以继续划分。
另外,每个参数化测试通常需要单独一个测试类。
- 测试类名以Test为后缀,最好也以被测类的类名和被测方法名为前缀。例如AccountWithdrawTest
这样做一方面从可读性来说表现更好,另一方面可以方便构建系统根据命名模式筛选出真正的测试类。
在src/test/java目录中,除了测试类之外也可能包含一些辅助类、工具类。也可能存在测试基类,作为其他测试类的共同父类。这个时候制定测试类的命名模式就很重要了。下面是maven中的单元测试插件surefire的配置:它只选择了类名以Test为后缀,并且前缀不是Abstract和Base的类来执行测试:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
<exclude>**/Base*.java</exclude>
</excludes>
</configuration>
</plugin>
这一章我们就讲到这里,下一章我们讲讲“使用JUnit编写和执行单元测试”!
-THE END-
原创作者 | 杨宇Yangyu
编程道与术原创内容
转载请注明“编程道与术”出处