1. 概述

在这个简短的教程中,我们将使用JUnit5的@Timeout注解以声明式的方式为单元测试设置超时时间。我们将讨论其使用方法,并观察它与@Parameterized@Nested测试的交互。

2. @Timeout注解

我们可以在JUnit5的单元测试上使用@Timeout注解来指定其运行的最大秒数;如果超过这个值,测试将因java.util.concurrent.TimeoutException而失败:

@Test
@Timeout(1)
void shouldFailAfterOneSecond() throws InterruptedException {
    Thread.sleep(10_000);
}

2.1. valueunit属性

我们已经了解了如何通过指定秒数来设置测试的超时时间。但是,我们可以利用注解的valueunit属性来指定不同的度量单位:

@Test
@Timeout(value = 2, unit = TimeUnit.MINUTES)
void shouldFailAfterTwoMinutes() throws InterruptedException {
    Thread.sleep(10_000);
}

2.2. threadMode属性

假设我们有一个运行缓慢的测试,因此需要一个较大的超时。为了高效执行,我们应该在单独的线程上运行这个测试,而不是阻塞其他测试。通过JUnit5的并行测试执行功能(/junit-5-parallel-tests)可以实现这一点。

另一方面,@Timeout注解本身提供了优雅地通过threadMode属性来实现这一目标的方法:

@Test
@Timeout(value = 5, unit = TimeUnit.MINUTES, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
void shouldUseADifferentThread() throws InterruptedException {
    System.out.println(Thread.currentThread().getName());
    Thread.sleep(10_000);
}

我们可以通过运行测试并打印当前线程的名称来验证这一点,它应该显示类似“junit-timeout-thread-1”的内容。

3. @Timeout的目标

如前所述,@Timeout注解可以方便地应用于单个测试方法。但也可以在类级别放置注解来为整个类的每个测试设置默认超时时间。如果没有在方法级别覆盖类级别的超时,那么如果超过这个值,未覆盖的测试也将失败:

@Timeout(5)
class TimeoutUnitTest {

    @Test
    @Timeout(1)
    void shouldFailAfterOneSecond() throws InterruptedException {
        Thread.sleep(10_000);
    }

    @Test
    void shouldFailAfterDefaultTimeoutOfFiveSeconds() throws InterruptedException {
        Thread.sleep(10_000);
    }
}

3.1. @Timeout@Nested测试

JUnit5的@Nested注解可以为单元测试创建嵌套类。我们可以将其与@Timeout一起使用。如果父类定义了一个默认超时值,那么嵌套类中的测试也会使用这个值:

@Timeout(5)
class TimeoutUnitTest {

    @Nested
    class NestedClassWithoutTimeout {
        @Test
    void shouldFailAfterParentsDefaultTimeoutOfFiveSeconds() throws InterruptedException {
            Thread.sleep(10_000);
    }
    }
}

然而,这个值可以在嵌套类级别或方法级别进行重写:

@Nested
@Timeout(3)
class NestedClassWithTimeout {

    @Test
    void shouldFailAfterNestedClassTimeoutOfThreeSeconds() throws InterruptedException {
        Thread.sleep(10_000);
    }

    @Test
    @Timeout(1)
    void shouldFailAfterOneSecond() throws InterruptedException {
        Thread.sleep(10_000);
    }
}

3.2. @Timeout@ParameterizedTest

我们可以利用@ParameterizedTest注解根据给定的一组输入值执行多个测试。我们可以在@ParameterizedTest上添加@Timeout,从而每个生成的测试都将使用指定的超时值。

例如,如果我们有一个将执行五个测试的@ParameterizedTest,并且我们将其注解为@Timeout(1),那么如果任何一个测试超过一秒,它将失败:

@Timeout(1)
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void eachTestShouldFailAfterOneSecond(int input) throws InterruptedException {
    Thread.sleep(1100);
}

4. 总结

在这篇文章中,我们讨论了JUnit5新的@Timeout注解。我们学习了如何配置它以及如何以声明式方式为我们的单元测试设置超时值。

一如既往,本文的完整源代码可在GitHub上查看:https://github.com/eugenp/tutorials/tree/master/testing-modules/junit5-annotations