maven JUnit测试类顺序

s3fp2yjn  于 2023-10-17  发布在  Maven
关注(0)|答案(7)|浏览(204)

我有一个带有Maven和JUnit的Java应用程序用于测试,带有故障安全和万无一失的插件。
我有2000多个集成测试。为了加快测试运行速度,我使用故障保护jvmfork来并行运行我的测试。我有一些繁重的测试类,它们通常在我的测试执行结束时运行,这会减慢我的CI验证过程。filesafe runorder:balanced对我来说是一个很好的选择,但我不能使用它,因为jvmfork。重命名测试类或移动到另一个包并按字母顺序运行它不是一个选择。有什么建议可以让我在验证过程开始时运行我的慢测试类吗?

nfzehxib

nfzehxib1#

JUnit 5(从5.8.0版本开始)中,测试类也可以订购。

  • src/test/resources/* junit-platform.properties**:
# ClassOrderer$OrderAnnotation sorts classes based on their @Order annotation
junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$OrderAnnotation

其他Junit内置类排序器实现:

org.junit.jupiter.api.ClassOrderer$ClassName
org.junit.jupiter.api.ClassOrderer$DisplayName
org.junit.jupiter.api.ClassOrderer$Random

有关设置配置参数的其他方法(在 junit-platform.properties 文件旁边),请参见JUnit 5用户指南。
您也可以提供自己的排序器。必须实现ClassOrderer接口:

package foo;
public class MyOrderer implements ClassOrderer {
    @Override
    public void orderClasses(ClassOrdererContext context) {
        Collections.shuffle(context.getClassDescriptors());
    }
}
junit.jupiter.testclass.order.default=foo.MyOrderer

请注意,@Nested测试类不能按ClassOrderer排序。

请参阅JUnit 5文档和ClassOrderer API docs以了解更多信息。

yqkkidmi

yqkkidmi2#

我给了我找到的答案的组合一个尝试:

第二个答案是基于这个github项目的这些classes,它在BSD-2许可下可用。
我定义了几个测试类:

public class LongRunningTest {

    @Test
    public void test() {

        System.out.println(Thread.currentThread().getName() + ":\tlong test - started");

        long time = System.currentTimeMillis();
        do {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        } while(System.currentTimeMillis() - time < 1000);

        System.out.println(Thread.currentThread().getName() + ":\tlong test - done");
    }
}
@Concurrent
public class FastRunningTest1 {

    @Test
    public void test1() {
        try {
            Thread.sleep(250);
        } catch (InterruptedException e) {
        }

        System.out.println(Thread.currentThread().getName() + ":\tfrt1-test1 - done");
    }

    // +7 more repetions of the same method
}

然后我定义了测试套件:
(FastRunningTest 2是第一个类的副本,具有调整后的输出)

@SuiteClasses({LongRunningTest.class, LongRunningTest.class})
@RunWith(Suite.class)
public class SuiteOne {}

@SuiteClasses({FastRunningTest1.class, FastRunningTest2.class})
@RunWith(Suite.class)
public class SuiteTwo {}

@SuiteClasses({SuiteOne.class, SuiteTwo.class})
@RunWith(ConcurrentSuite.class)
public class TopLevelSuite {}

当我执行TopLevelSuite时,我得到以下输出:
TopLevelSuite-1-thread-1: long test - started FastRunningTest1-1-thread-4: frt1-test4 - done FastRunningTest1-1-thread-2: frt1-test2 - done FastRunningTest1-1-thread-1: frt1-test1 - done FastRunningTest1-1-thread-3: frt1-test3 - done FastRunningTest1-1-thread-5: frt1-test5 - done FastRunningTest1-1-thread-3: frt1-test6 - done FastRunningTest1-1-thread-1: frt1-test8 - done FastRunningTest1-1-thread-5: frt1-test7 - done FastRunningTest2-2-thread-1: frt2-test1 - done FastRunningTest2-2-thread-2: frt2-test2 - done FastRunningTest2-2-thread-5: frt2-test5 - done FastRunningTest2-2-thread-3: frt2-test3 - done FastRunningTest2-2-thread-4: frt2-test4 - done TopLevelSuite-1-thread-1: long test - done TopLevelSuite-1-thread-1: long test - started FastRunningTest2-2-thread-5: frt2-test8 - done FastRunningTest2-2-thread-2: frt2-test6 - done FastRunningTest2-2-thread-1: frt2-test7 - done TopLevelSuite-1-thread-1: long test - done
它基本上显示了LongRunningTest是与FastRunningTests并行执行的。Concurrent Annotation定义的用于并行执行的线程的默认值是5,可以在FastRunningTests的并行执行的输出中看到。
缺点是这些Threads不在FastRunningTest1FastRunningTest2之间共享。
这种行为表明,它是“有点”可能做什么,你想做的(所以是否与您目前的设置是一个不同的问题)。
我也不确定这是否真的值得努力,

  • 因为您需要手动准备这些TestSuites(或编写一些自动生成它们的东西)
  • 你需要为所有这些类定义并发注解(可能每个类有不同数量的threads

由于这基本上表明可以定义类的执行顺序并触发它们的并行执行,因此也可以让整个过程只使用一个ThreadPool(但我不确定这意味着什么)。
由于整个概念是基于ThreadPoolExecutor,使用PriorityBlockingQueue,它为长时间运行的任务提供了更高的优先级,您将更接近于首先执行长时间运行的测试的理想结果。
我做了更多的实验,实现了自己的自定义suite runner和junit runner。背后的想法是让你的JUnitRunner将测试提交到一个由单个ThreadPoolExecutor处理的队列中。因为我没有在RunnerScheduler#finish方法中实现阻塞操作,所以我最终得到了一个解决方案,在这个解决方案中,所有类的测试都在执行开始之前传递到队列。(如果涉及更多的测试类和方法,这可能会有所不同)。
至少它证明了一点,如果你真的想的话,你可以在这个级别上摆弄junit。
我的poc的代码有点乱,放在这里太长了,但是如果有人感兴趣,我可以把它推到github项目中。

3hvapo4f

3hvapo4f3#

junit5.8.0-M1版本中有一个解决方案。
基本上,你需要创建自己的排序器。我做过类似的事。
下面是一个注解,你将在测试类中使用它:

@Retention(RetentionPolicy.RUNTIME)
public @interface TestClassesOrder {
    public int value() default Integer.MAX_VALUE;
}

然后,您需要创建将实现org.junit.jupiter.API.ClassOrderer的类

public class AnnotationTestsOrderer implements ClassOrderer {
    @Override
    public void orderClasses(ClassOrdererContext context) {
        Collections.sort(context.getClassDescriptors(), new Comparator<ClassDescriptor>() {
            @Override
            public int compare(ClassDescriptor o1, ClassDescriptor o2) {
                TestClassesOrder a1 = o1.getTestClass().getDeclaredAnnotation(TestClassesOrder.class);
                TestClassesOrder a2 = o2.getTestClass().getDeclaredAnnotation(TestClassesOrder.class);
                if (a1 == null) {
                    return 1;
                }

                if (a2 == null) {
                    return -1;
                }
                if (a1.value() < a2.value()) {
                    return -1;
                }

                if (a1.value() == a2.value()) {
                    return 0;
                }

                if (a1.value() > a2.value()) {
                    return 1;
                }
                return 0;
            }
        });
    }
}

为了让它工作,你需要告诉junit你将使用哪个类来对描述符进行排序。因此,您需要创建文件“junit-platform.properties”,它应该在resources文件夹中。在这个文件中,你只需要一行命令你的排序器类:

junit.jupiter.testclass.order.default=org.example.tests.AnnotationTestOrderer

现在你可以像Order annotation一样使用你的orderer annotation,但是是在类级别上:

@TestClassesOrder(1)
class Tests {...}

@TestClassesOrder(2)
class MainTests {...}

@TestClassesOrder(3)
class EndToEndTests {...}

我希望这将帮助某人。

dced5bon

dced5bon4#

在我们的项目中,我们创建了一些标记接口(例如

public interface SlowTestsCategory {}


并将其放入带有慢速测试的测试类中JUnit的@Category注解中。

@Category(SlowTestsCategory.class)

之后,我们为Gradle创建了一些特殊的任务,以便按类别或按自定义顺序运行几个类别的测试:

task unitTest(type: Test) {
  description = 'description.'
  group = 'groupName'

  useJUnit {
    includeCategories 'package.SlowTestsCategory'
    excludeCategories 'package.ExcludedCategory'
  }
}

这个解决方案由Gradle提供,但也许它对你有帮助。

gpnt7bae

gpnt7bae5#

让我先总结一下,然后再提出建议。
1.集成测试很慢。这很好,很自然。

  1. CI构建不会运行假设系统部署的测试,因为CI中没有部署。我们关心的是CD过程中的部署。所以我假设你的集成测试不假设部署。
  2. CI构建首先运行单元测试。单元测试非常快,因为它们只使用RAM。
    我们从单元测试中得到了良好和快速的反馈。
    在这一刻,我们确信我们没有问题,得到快速反馈。但我们仍然希望更快地运行集成测试。我建议以下解决方案:
    1.改进实际测试。它们往往是无效的,可以大大加快速度。
    1.在后台运行集成测试(即不要等待他们的真实的时间反馈)。
    它们比单元测试慢得多是很自然的。
    1.如果您需要更快地从其中一些测试中获得反馈,请将集成测试拆分到组中,并单独运行它们。
    1.在不同的JVM中运行集成测试。而不是同一个JVM中的不同线程!
    在这种情况下,您不关心线程安全,也不应该关心它。
    1.在不同的机器上运行集成测试等等。
    我参与了许多不同的项目(其中一些项目的CI构建运行了48小时),前3步就足够了(即使是疯狂的情况)。步骤#4很少需要良好的测试。步骤#5是针对特定的情况。
    你会看到我的建议与过程有关,而不是与工具有关,因为问题就在过程中。
    人们经常忽略根本原因,并试图调优工具(本例中为Maven)。他们得到了外观上的改进,但所创建的解决方案的维护成本很高。
e0uiprwp

e0uiprwp6#

这是另一种可能的方式,在某些情况下可能是有用的。
例如,如果我想让所有的UI测试(需要很长时间才能执行)在常规单元测试之后运行。
为此,可以创建一个单独的源集,然后在常规测试后运行它。
build.gradle.kts:

sourceSets {
    create("uiTest") {
        // Adds files from the main source set to the compile classpath and runtime classpath of this new source set.
        // sourceSets.main.output is a collection of all the directories containing compiled main classes and resources
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output
    }
}
// Makes the uiTest configurations extend from test configurations,
// which means that all the declared dependencies of the test code (and transitively the main as well)
// also become dependencies of this new configuration
val uiTestImplementation by configurations.getting {
    extendsFrom(configurations.testImplementation.get())
}
val uiTestRuntimeOnly by configurations.getting {
    extendsFrom(configurations.testRuntimeOnly.get())
}
val uiTest = task<Test>("uiTest") {
    description = "Runs UI tests."
    group = "verification"

    testClassesDirs = sourceSets["uiTest"].output.classesDirs
    classpath = sourceSets["uiTest"].runtimeClasspath
    mustRunAfter("test") // The important part

    testLogging {
        events(TestLogEvent.PASSED)
    }
}
tasks.check { dependsOn(uiTest) }

tasks.withType<Test> {
    useJUnitPlatform() // Enables JUnit 5 (along with JUnit 4) tests
}
src
  --- main
      --- java
      --- resources
  --- test
      --- java
      --- resources
  --- uiTest
      --- java
      --- resources

像在src/test/java/...中一样,在src/uiTest/java/...目录中创建UI测试
现在,如果您执行check Gradle任务(./gradlew check)(这只是一个执行其他测试任务的任务),则UI测试将在常规单元测试之后执行。
旁注:

  • 可以通过执行./gradlew uiTest任务来执行UI测试
  • 可以通过执行./gradlew test任务来执行单元测试
ou6hu8tu

ou6hu8tu7#

您可以使用Junit 5中的注解来设置您希望使用的测试顺序:用途:
Junit 5用户指南:https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-execution-order

import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {

    @Test
    @Order(1)
    void nullValues() {
        // perform assertions against null values
    }

    @Test
    @Order(2)
    void emptyValues() {
        // perform assertions against empty values
    }

    @Test
    @Order(3)
    void validValues() {
        // perform assertions against valid values
    }

}

升级到Junit5可以相当容易地完成,文章开头的链接上的文档包含了您可能需要的所有信息。

相关问题