在 Gradle 中,如何在每次执行 JUnit 测试类之前设置唯一的系统属性?
Posted
技术标签:
【中文标题】在 Gradle 中,如何在每次执行 JUnit 测试类之前设置唯一的系统属性?【英文标题】:In Gradle, how to set unique system property before each execution of JUnit test class? 【发布时间】:2017-12-09 20:23:17 【问题描述】:在下面的 gradle 脚本中,maxParallelForks
和 forkEvery
将为每个 Junit 测试类使用一个新的 JVM 启动一个新进程。 有没有办法让每个测试进程/JVM/Junit 类都有自己独特的系统属性?
我尝试了一个实验,在 beforeSuite
方法中将 UUID 值作为系统属性传递。
apply plugin:'java'
test
ignoreFailures = true
maxParallelForks = 8
forkEvery = 1
systemProperty 'some.prop', 'value'
beforeSuite descriptor ->
logger.lifecycle("Before suite: " + descriptor)
def theuuid = UUID.randomUUID().toString()
println "beforeSuite uuid =" + theuuid
systemProperty 'some.uuid', theuuid
beforeTest descriptor ->
logger.lifecycle("Running test: " + descriptor)
repositories
mavenCentral()
dependencies
testCompile 'junit:junit:4.12'
JUnit 测试类LoginTest1
和LoginTest2
在测试方法中打印出系统属性。
public class LoginTest1
private String userName="Gradle";
@Test
public void testUserName() throws Exception
System.out.println("LoginTest1: Testing Login ");
System.out.println("LoginTest1: Testing Login some.prop -> "
+ System.getProperty("some.prop"));
System.out.println("LoginTest1: Testing Login some.uuid -> "
+ System.getProperty("some.uuid"));
assertTrue(userName.equals("Gradle"));
public class LoginTest2
private String userName="Gradle";
@Test
public void testUserName() throws Exception
System.out.println("LoginTest2: Testing Login ");
System.out.println("LoginTest2: Testing Login some.prop -> "
+ System.getProperty("some.prop"));
System.out.println("LoginTest2: Testing Login some.uuid -> "
+ System.getProperty("some.uuid"));
assertTrue(userName.equals("Gradle"));
从下面的 gradle 输出中可以看到,beforeSuite
方法在运行各个 JUnit 测试方法之前被调用。两个测试类都获得相同的系统属性6a006689-474f-47d5-9649-a88c92b45095
$ gradle clean test
> Task :test
Before suite: Gradle Test Run :test
beforeSuite uuid =6a006689-474f-47d5-9649-a88c92b45095
Before suite: Gradle Test Executor 1
beforeSuite uuid =39edb608-3027-4ad8-bd15-f52f50175582
Before suite: Test class ch6.login.LoginTest1
beforeSuite uuid =c0ce55e0-924c-4319-becb-bc27229c9cf3
Before suite: Gradle Test Executor 2
beforeSuite uuid =c15eaac0-7ea7-46d2-8235-8dc49b3f7a39
Running test: Test testUserNameNotNull(ch6.login.LoginTest1)
Before suite: Test class ch6.login.LoginTest2
beforeSuite uuid =73abbe24-5b78-4935-867a-445ac4ac20ef
Running test: Test testUserName(ch6.login.LoginTest1)
Running test: Test testUserName(ch6.login.LoginTest2)
JUnit 测试类输出:
LoginTest1: Testing Login
LoginTest1: Testing Login some.prop -> value
LoginTest1: Testing Login some.uuid -> 6a006689-474f-47d5-9649-a88c92b45095
LoginTest2: Testing Login
LoginTest2: Testing Login some.prop -> value
LoginTest2: Testing Login some.uuid -> 6a006689-474f-47d5-9649-a88c92b45095
看起来有一个对 beforeSuite 的初始调用(被视为“:test”),然后每个测试执行器调用一次。第一个 uuid 值(“6a006689”)在所有 JVM 中设置。 我可以使用什么类似于简单的 beforeClass 方法吗?
在下面的调试日志中,看起来测试执行器被赋予了 jvm 参数 -Dsome.uuid=6a006689-474f-47d5-9649-a88c92b45095
并且单独用于创建新的 JVM(根据 forkEvery
配置)。这让我觉得我想做的事情在 Gradle 中是不可能的。
20:23:42.466 [INFO] [org.gradle.process.internal.DefaultExecHandle] Starting process 'Gradle Test Executor 1'. Working directory: /a/b/ Command: /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Djava.security.manager=worker.org.gradle.process.internal.worker.child.BootstrapSecurityManager -Dorg.g
radle.native=false -Dsome.prop=value -Dsome.uuid=6a006689-474f-47d5-9649-a88c92b45095 -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -ea -cp /data/.gradle/caches/4.3.1/workerMain/gradle-worker.jar worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 1'
20:23:42.466 [INFO] [org.gradle.process.internal.DefaultExecHandle] Starting process 'Gradle Test Executor 2'. Working directory: /a/b/ Command: /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Djava.security.manager=worker.org.gradle.process.internal.worker.child.BootstrapSecurityManager -Dorg.g
radle.native=false -Dsome.prop=value -Dsome.uuid=6a006689-474f-47d5-9649-a88c92b45095 -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -ea -cp /data/.gradle/caches/4.3.1/workerMain/gradle-worker.jar worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 2'
2018 年 8 月更新
以下解决方案在 Gradle 4.9 及更高版本中不起作用
相反,有一个 org.gradle.test.worker 系统属性,您可以使用它来区分工作人员、计算显示名称、临时目录等。这个 test.worker 只是一个递增的 long 值。 Gradle 团队没有计划让 Test Workers 可配置。很遗憾,因为为了升级 Gradle,我不得不对我的测试代码进行一些重大的重构。 JUnit 5 中的“@BeforeAllCallback”可能对您有用。
试图通过拦截Test.copyTo(JavaForkOptions target)来解决
这段代码演示了 lance-java 的建议并成功回答了我的问题。我有以下 build.gradle 脚本,它使用 MyTestTask 扩展 Test 并添加一个称为my_uuid
的唯一值。请注意,构建脚本必须具有 forkEvery = 1 以确保每次 JUnit 测试调用一次 copyTo
:
class MyTestTask extends Test
public Test copyTo(JavaForkOptions target)
super.copyTo(target);
String uuid = UUID.randomUUID().toString();
System.out.println("MyTestTask copyTo:\n my_uuid=" + uuid);
System.out.println("\t before getJvmArgs:" + target.getJvmArgs());
System.out.println("\t before getSystemProperties:" + target.getSystemProperties());
target.systemProperty("my_uuid", uuid);
System.out.println("\t after getJvmArgs:" + target.getJvmArgs());
System.out.println("\t after getSystemProperties:" + target.getSystemProperties());
return this;
///
subprojects
apply plugin: 'java'
repositories
mavenCentral()
dependencies
testCompile 'junit:junit:4.12'
test
enabled false // This doesn't seem to matter.
task my_test(type: MyTestTask)
forkEvery 1 // need this so copyTo called each JUnit test
testLogging.showStandardStreams = true
doFirst
def uuid = UUID.randomUUID().toString()
println "TEST:Task name is $name. In doFirst. uuid is " + uuid
jvmArgs '-Dcommon_uuid='+ uuid
执行的 Junit 测试很简单,只需打印出 System.getProperty("my_uuid") 和 System.getProperty("common_uuid")。
> Task :CustomCopyToSubProject:my_test
TEST:Task name is my_test. In doFirst. uuid is eed1ee92-7805-4b8e-a74c-96218d1be6e1
MyTestTask copyTo:
MyTestTask copyTo:
my_uuid=a7996c20-1085-4397-9e32-fd84467dcb45
my_uuid=69f6f347-71d0-4e5d-9fad-66bf4468fab3
before getJvmArgs:[]
before getJvmArgs:[]
before getSystemProperties:[common_uuid:eed1ee92-7805-4b8e-a74c-96218d1be6e1]
before getSystemProperties:[common_uuid:eed1ee92-7805-4b8e-a74c-96218d1be6e1]
after getJvmArgs:[]
after getJvmArgs:[]
after getSystemProperties:[common_uuid:eed1ee92-7805-4b8e-a74c-96218d1be6e1, my_uuid:69f6f347-71d0-4e5d-9fad-66bf4468fab3]
after getSystemProperties:[common_uuid:eed1ee92-7805-4b8e-a74c-96218d1be6e1, my_uuid:a7996c20-1085-4397-9e32-fd84467dcb45]
ch3.LoginTest2 > testUserName2 STANDARD_OUT
LoginTest2.java: Testing Login ! >>---> my_uuid=a7996c20-1085-4397-9e32-fd84467dcb45 common_uuid=eed1ee92-7805-4b8e-a74c-96218d1be6e1
ch3.LoginTest1 > testUserName1 STANDARD_OUT
LoginTest1.java: Testing Login ! >>---> my_uuid=69f6f347-71d0-4e5d-9fad-66bf4468fab3 common_uuid=eed1ee92-7805-4b8e-a74c-96218d1be6e1
task my_test
在doFirst
中设置common_uuid
。 MyTestTask.copyTo
方法设置自己的my_uuid
。
您可以从输出中看到,MyTestTask 被调用了两次,每个 JUnit 测试任务(LoginTest1 和 LoginTest2)调用一次。这两个 JUnit 测试在 my_uuid
中获得了自己独特的系统属性。
【问题讨论】:
你想用那个 UUID 实现什么?如果每个测试都派生一个新的 JVM,你可以只使用一个用 UUID 初始化的静态字段。 我希望每个 JUnit 测试类都有一个唯一的 UUID。我不想要多个具有相同 UUID 的类。我应该让我的问题更清楚吗? @patronizing_bofh 该解决方案似乎不适用于 gradle 4.9 。copyTo
方法只被调用一次。你用的是什么版本?
@L4zy 不,不幸的是,这不适用于 gradle 4.9。有一个 org.gradle.test.worker 系统属性,您可以使用它来区分工作人员、计算显示名称、临时目录等。这个 test.worker 只是增加 long 值。 Gradle 团队没有计划让 Test Workers 可配置。很遗憾,因为为了升级 Gradle,我不得不对我的测试代码进行一些重大的重构。 JUnit 5 中的“@BeforeAllCallback”可能对你有用。
【参考方案1】:
我想你想拦截Test.copyTo(JavaForkOptions target)
,不幸的是我能看到的唯一方法是扩展Test
例如
public class Test2 extends org.gradle.api.tasks.testing.Test
public Test copyTo(JavaForkOptions target)
super.copyTo(target);
target.systemProperty("uuid", UUID.randomUUID().toString());
return this;
build.gradle
apply plugin: 'java'
test
enabled = false
task test2(type: Test2)
// guessing you'll need to configure a bit of stuff here
check.dependsOn test2
【讨论】:
您好@lance-java,我尝试了您的建议,但没有成功。不过,看起来每个 jvm 都调用了copyTo
,这很好。我无法将唯一的系统属性传递给相应的 jvm。我在标题“尝试通过拦截 Test.copyTo(JavaForkOptions 目标)”下记录了我在原始帖子中所做的事情
我认为您没有为新的测试任务配置源,所以它可能没有运行任何测试?另外我看不到您禁用默认测试任务,所以输出可能来自默认任务?你最后打电话给super.copyTo(target);
。我建议先打电话给这个。这可能会覆盖您设置的任何系统属性。不确定,但值得一试。
默认源工作正常,您可以在我发布的输出中看到调用的 JUnit 测试。禁用默认测试任务无效。 super.copyTo(target)
是问题所在!我根据您的(正确)建议使用工作代码更新了我的“尝试...”部分!谢谢!【参考方案2】:
由于每个测试都会派生一个新的 JVM,因此您可以只使用一个使用 UUID 初始化的静态字段。
public class Helper
public static final String uuid = UUID.randomUUID().toString();
public class LoginTest2
private String userName="Gradle";
@Test
public void testUserName() throws Exception
System.out.println("LoginTest2: Testing Login ");
System.out.println("LoginTest2: Testing Login some.uuid -> "
+ Helper.uuid;
assertTrue(userName.equals("Gradle"));
【讨论】:
是的,我只使用 Java 就可以做到这一点,但是否有可能在 Gradle 中完成这一切? 我不知道 是不是因为有一个共享的JavaForkOptions
,正如这个问题中提到的:(***.com/questions/28780841/…)?我在调试日志输出中看到了一些东西。以上是关于在 Gradle 中,如何在每次执行 JUnit 测试类之前设置唯一的系统属性?的主要内容,如果未能解决你的问题,请参考以下文章
当 JUnit 测试失败时,Mark Gradle 在 Jenkins 中构建不稳定
如何通过 Gradle 测试任务在我的 JUnit 上启用调试