在 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 脚本中,maxParallelForksforkEvery 将为每个 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 测试类LoginTest1LoginTest2 在测试方法中打印出系统属性。

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_testdoFirst 中设置common_uuidMyTestTask.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 5?

如何将 JUnit 5 与 Gradle 一起使用?

如何通过 Gradle 测试任务在我的 JUnit 上启用调试

如何在 Gradle 构建文件中正确排除“junit-vintage-engine”?

如何在多 Gradle 项目的 JUnit Spring 上下文中加载资源属性?