Java单元测试实践-00.目录(9万多字文档+700多测试示例)
1. 使用Gradle执行单元测试
当需要使单元测试执行支持自动化时,需要选择合适的构建工具,以下说明如何使用Gradle支持单元测试自动化执行。
1.1. Gradle test任务
参考 https://docs.gradle.org/current/userguide/java_testing.html#sec:java_testing_basics ,所有JVM测试都围绕一种任务类型进行,即test。
参考 https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_tasks ,test任务可以使用JUnit或TestNG运行单元测试。
参考 https://docs.gradle.org/current/userguide/java_testing.html#sec:test_detection ,默认情况下,Gradle将运行它检测到的所有测试类。
1.2. 使用Gradle Wrapper
参考 https://docs.gradle.org/current/userguide/gradle_wrapper.html ,建议使用Gradle Wrapper执行任意Gradle构建方法。Wrapper是一个脚本,它调用已声明版本的Gradle,并在必要时提前下载。因此,开发人员可以快速启动并运行Gradle项目,无需手动安装,在切换使用的Gradle版本时非常方便。
1.2.1. 添加Gradle Wrapper
参考 https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:adding_wrapper ,在Java项目根目录执行“gradle wrapper命令”,可将Gradle Wrapper所需文件自动拷贝至项目中。“–gradle-version”参数可指定下载的Gradle版本,“–distribution-type”参数可指定Gradle发布包类型,可选值为bin或all,默认为bin。
gradle wrapper
gradle wrapper --gradle-version 5.6.2 --distribution-type all
以上命令执行成功后,在项目中会添加以下目录及文件:
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
文件 | 使用 |
gradle-wrapper.jar | 用于下载Gradle发布包的Jar包 |
gradle-wrapper.properties | 配置Wrapper运行时行为的配置文件,包含与该版本兼容的Gradle版本等 |
gradlew/gradlew.bat | Shell脚本,及Windows批处理脚本,用于使用Wrapper执行构建 |
当通过Gradle Wrapper下载Gradle发布包成功后,Gradle发布包会被解压到当前用户目录,或GRADLE_USER_HOME环境变量对应目录中的,格式为“wrapper\dists\gradle-[版本号]-[发布包类型]\[字符串]\gradle-[版本号]”,例如“wrapper\dists\gradle-6.1-bin\74ow5f78ml5iin9568hvtcqz9\gradle-6.1”。
1.2.2. 通过Gradle Wrapper执行Gradle任务
当通过Gradle Wrapper执行Gradle任务时,需要使用项目目录中的gradlew/gradlew.bat脚本,在Linux环境可使用gradlew命令,在Windows环境可使用gradlew/gradlew.bat命令(.bat可省略)。
1.3. Gradle test任务依赖的任务
1.3.1. 查看Gradle test任务依赖的任务
参考Gradle对于任务的说明 https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_tasks ,test任务直接及间接依赖的其他任务如下所示。
test
-testClasses
--compileTestJava
---classes
----compileJava
-----clean
----processResources
--processTestResources
参考 https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_execution_options ,在执行Gradle命令时,若指定了“–dry-run”参数,会禁用所有的任务,可用于显示会执行的任务。也可使用“-m”参数替代。
当执行“gradle/gradlew test -m”时,输出结果如下所示,与Gradle对于任务的说明一致。以下任务的显示顺序,与实际执行顺序一致。
:clean SKIPPED
:compileJava SKIPPED
:processResources SKIPPED
:classes SKIPPED
:compileTestJava SKIPPED
:processTestResources SKIPPED
:testClasses SKIPPED
:test SKIPPED
1.3.2. Gradle test任务依赖的任务说明
任务 | 说明 |
clean | 删除项目build目录 |
compileJava | 使用JDK编译器编译生产Java源文件 |
processResources | 将生产资源文件复制到生产资源目录中 |
classes | 一个聚合任务,只是依赖了其他任务。其他插件可能会附加其他编译任务 |
compileTestJava | 使用JDK编译器编译测试Java源文件 |
processTestResources | 将测试资源文件复制到测试资源目录中 |
testClasses | 一个聚合任务,只是依赖了其他任务。其他插件可能会附加其他测试编译任务 |
1.4. Gradle执行test任务时的相关进程
参考 https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html 、 https://docs.gradle.org/current/userguide/java_testing.html#sec:test_execution 。在执行JUnit (3.8.x, 4.x or 5.x)或TestNG测试时,总是在一个或多个独立的(forked)JVM中执行测试,与主要构建过程隔离。这样可以防止类路径污染或构建过程中过多的内存消耗,还允许使用与构建过程不同的JVM参数执行测试。
使用“gradlew/gradle test”命令执行test任务时,使用“jps”命令查看Gradle启动的Java进程的PID及主类,包含主类为“GradleDaemon”“GradleMain”“GradleWorkerMain”等进程,使用“gradlew test”命令时,“GradleMain”会变为“GradleWrapperMain”。
1.4.1. GradleDaemon进程
GradleDaemon进程可参考“The Gradle Daemon”( https://docs.gradle.org/current/userguide/gradle_daemon.html )。
1.4.2. GradleMain/GradleWrapperMain进程
GradleMain/GradleWrapperMain进程用于执行Gradle的test及同时指定的其他任务。
当使用gradle命令时,对应的进程为GradleMain;当使用gradlew命令时,对应的进程为GradleWrapperMain。
使用“jinfo [PID]”命令查看GradleMain/GradleWrapperMain进程的系统属性及JVM启动参数,“sun.java.command”参数包含了test及同时指定的其他任务和JVM启动参数。
例如执行“gradle test”命令时,“sun.java.command”参数值为“org.gradle.launcher.GradleMain test”;
执行“gradlew test -DtestMode=fast jacocoTestReport”命令时,“sun.java.command”参数值为“org.gradle.wrapper.GradleWrapperMain test -DtestMode=fast jacocoTestReport”。
1.4.3. GradleWorkerMain进程
GradleWorkerMain进程用于执行测试类。
使用“jinfo”命令查看GradleWorkerMain进程的“java.class.path”参数,其中包含了项目中依赖的全部Jar包。
使用“jmap -histo [PID]|findstr [关键字]”命令,查找GradleWorkerMain进程加载的类,例如“jmap -histo 30256|findstr adrninistrator”,可确认GradleWorkerMain进程加载了测试类。
1.5. Gradle build任务排除test任务
参考 https://docs.gradle.org/current/userguide/java_plugin.html#lifecycle_tasks 。
build任务是聚合执行完整项目构建的任务。
build任务依赖check任务,check任务依赖test任务,即build任务间接依赖了test任务。
assemble任务依赖jar任务,即build任务间接依赖了jar任务。
参考 https://docs.gradle.org/current/userguide/war_plugin.html#sec:war_tasks 。
War插件添加了war任务,修改了assemble任务使其依赖war任务,即build任务也可以依赖war任务。
当执行build任务时,会执行依赖的jar/war任务,生成对应的jar包或war包,也会执行test任务。
参考 https://docs.gradle.org/current/userguide/command_line_interface.html#sec:excluding_tasks_from_the_command_line 。
在执行Gradle任务时,可使用-x或–exclude-task选项排除执行的任务,例如执行build任务时排除test任务的命令如下:
gradlew/gradle build -x test
可对比“gradlew/gradle build -x test -m”与“gradlew/gradle build -m”的执行结果,test任务及相关的compileTestJava、processTestResources、testClasses任务均被排除。
1.6. 测试类过滤
参考Gradle文档“Test filtering”( https://docs.gradle.org/current/userguide/java_testing.html#test_filtering )。
运行测试集的子集是常见的需求,Gradle提供了两种机制来执行此操作:
- 过滤(推荐的选项)
- 测试包含/排除
过滤取代了包含/排除机制。
使用Gradle的测试过滤,可以根据以下条件选择要运行的测试:
- 完全限定的类名称或完全限定的方法名称,例如:org.gradle.SomeTest,org.gradle.SomeTest.someMethod;
- 一个简单的类名或方法名(如果以大写字母开头),例如:SomeTest,SomeTest.someMethod(自Gradle 4.7起);
- '*'通配符匹配。
可以在构建脚本或通过–tests命令行选项启用过滤。
- 在Gralde脚本中使用测试过滤
在Gralde脚本中使用测试过滤,可参考 https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/TestFilter.html 。
使用includeTestsMatching/excludeTestsMatching可以指定单条需要包含/排除的测试配置;使用setIncludePatterns()/setExcludePatterns()方法可以指定多条测试配置。如下所示。
test {
filter {
includeTestsMatching "*UiCheck"
includeTestsMatching "org.gradle.internal.*"
includeTestsMatching "*IntegTest"
setIncludePatterns('adrninistrator.test.testframework.*')
setExcludePatterns('adrninistrator.test.testframework.junit.*', '*.TestSpring1*')
}
}
- 通过–tests命令行选项使用测试过滤
命令行选项对于执行单个测试方法特别有用。使用–tests时,构建脚本中声明的包含仍然生效。可以指定多个–tests选项,所有选项都会生效。
–tests选项支持以下两种使用方法:
- 简单名称模式
从4.7版本开始,Gradle将以大写字母开头的模式作为简单的类名或类名+方法名。例如,以下命令行可以运行SomeTestClass测试用例中的所有测试,也可以只运行其中的一个,而无论其位于哪个程序包中:
# 执行SomeTestClass类中的全部测试方法
gradlew/gradle test --tests SomeTestClass
# 执行SomeTestClass类中的指定方法
gradlew/gradle test --tests SomeTestClass.someSpecificMethod
gradlew/gradle test --tests SomeTestClass.*someMethod*
- 完全限定的名称模式
在Gradle 4.7之前的版本中,或4.7及之后的版本但模式不以大写字母开头时,会被当作完全限定的名称模式处理。因此,如果要使用测试类名称而不考虑其包,需要使用“–tests *.SomeTestClass”格式。支持“containing”“all.in”等语法,示例如下:
# 指定测试类
gradlew/gradle test --tests org.gradle.SomeTestClass
# 指定测试类与方法
gradlew/gradle test --tests org.gradle.SomeTestClass.someSpecificMethod
# 包含指定字符串的方法名
gradlew/gradle test --tests "org.gradle.SomeTestClass.some method containing spaces"
# 指定包中的全部类(递归地)
gradlew/gradle test --tests 'all.in.specific.package*'
# 指定包中的指定方法(递归地)
gradlew/gradle test --tests 'all.in.specific.package*.someSpecificMethod'
gradlew/gradle test --tests '*IntegTest'
gradlew/gradle test --tests '*IntegTest*ui*'
通配符“*”对于包名的分隔符“.”没有特殊处理,完全基于文本。因此“–tests *.SomeTestClass”格式的模式会匹配任意包,无论其深度。
在Gradle 4.x版本中,“-Dtest.single”命令行选项与“–tests”作用类似,在Gradle 5开始的版本,上述选项被废弃,可参考 https://docs.gradle.org/current/userguide/upgrading_version_4.html#changes_5.0 。
1.7. 测试类检测
参考Gradle文档“Test detection”( https://docs.gradle.org/current/userguide/java_testing.html#sec:test_detection )。
默认情况下,Gradle会运行检测到的所有测试,通过检查已编译的测试类完成。根据测试框架使用不同的检测标准。
对于JUnit,Gradle会扫描JUnit 3与4测试类。如果某个类符合以下条件,则将其视为JUnit测试:
- 最终继承自TestCase或GroovyTestCase;
- 使用了@RunWith注释;
- 包含使用@Test注释的方法,或超类满足该条件。
对于TestNG,Gradle扫描使用@Test注释的方法。
请注意,抽象类不会被执行。另外,Gradle会对测试类路径中的jar文件扫描继承树,如果这些jar文件包含测试类,也会被运行。
在JUnit平台中,只有includes与excludes参数用于过滤测试类,scanForTestClasses参数不生效。
在Gradle的test任务中,可通过include与exclude参数分别指定需要包含及排除的测试类。
参考 https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html ,示例为’org/foo/**’,’**/*Test.class’,支持通配符,支持指定多项配置。
当仅需要通过类名匹配时,需要将包名指定为“**”,例如’**/TestSuite**’。
使用include或exclude参数示例如下:
test {
include '**'
exclude 'adrninistrator/test/testdatabase/**', 'adrninistrator/test/testmock/mybatis/**', '**/TestSuite**'
}
可以使用setIncludes()、setExcludes()方法替代include、exclude参数,可参考 https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/Test.html#setIncludes-java.lang.Iterable- 。
在使用setIncludes()、setExcludes()方法时,参数需要为合集形式,可在test任务的doFirst()方法中指定,示例如下:
test {
doFirst {
setIncludes(['adrninistrator/test/testframework/**'])
setExcludes(['adrninistrator/test/testframework/junit/**', '**/TestSpring1**'])
}
}
1.8. 并发执行测试
参考 https://docs.gradle.org/current/userguide/java_testing.html#sec:test_execution 、 https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:maxParallelForks 、 https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/Test.html#setMaxParallelForks-int- 。
Gradle的test任务的maxParallelForks参数,可指定Gradle并发执行测试的进程数。
maxParallelForks参数默认值为1,即Gradle默认使用一个进程执行测试类。
将maxParallelForks参数指定为其他正整数值时,可以使用Gradle同时启动多个测试进行执行测试类,可以减少执行时间。
增大maxParallelForks参数值进行并发测试时,执行速度提升的程度有待观察。需要注意检查测试代码在并发情况下是否会出错,比如生成唯一流水号等情况。
示例如下:
test {
doFirst {
// 指定同时执行的测试类数量,默认为1
maxParallelForks = 1
}
}