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提供了两种机制来执行此操作:

  1. 过滤(推荐的选项)
  2. 测试包含/排除

过滤取代了包含/排除机制。

使用Gradle的测试过滤,可以根据以下条件选择要运行的测试:

  1. 完全限定的类名称或完全限定的方法名称,例如:org.gradle.SomeTest,org.gradle.SomeTest.someMethod;
  2. 一个简单的类名或方法名(如果以大写字母开头),例如:SomeTest,SomeTest.someMethod(自Gradle 4.7起);
  3. '*'通配符匹配。

可以在构建脚本或通过–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选项支持以下两种使用方法:

  1. 简单名称模式

从4.7版本开始,Gradle将以大写字母开头的模式作为简单的类名或类名+方法名。例如,以下命令行可以运行SomeTestClass测试用例中的所有测试,也可以只运行其中的一个,而无论其位于哪个程序包中:

# 执行SomeTestClass类中的全部测试方法
gradlew/gradle test --tests SomeTestClass

# 执行SomeTestClass类中的指定方法
gradlew/gradle test --tests SomeTestClass.someSpecificMethod

gradlew/gradle test --tests SomeTestClass.*someMethod*
  1. 完全限定的名称模式

在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
    }
}