JUnit 5中的@TestInstance注解有什么用途?

67

你能简要说明一下JUnit 5中的@TestInstance注解以及它如何有用吗?

我认为我们可能可以通过将我们的字段设为静态的static来实现相同的效果。

5个回答

53

我认为文档提供了一个有用的概述:

如果您希望JUnit Jupiter在同一个测试实例上执行所有测试方法,只需使用@TestInstance(Lifecycle.PER_CLASS)为测试类加注释。使用此模式时,每个测试类将创建一个新的测试实例。因此,如果您的测试方法依赖于存储在实例变量中的状态,则可能需要在@BeforeEach或@AfterEach方法中重置该状态。

与默认的“per-method”模式相比,“per-class”模式具有一些额外的好处。特别是,在“per-class”模式下,可以在非静态方法以及接口默认方法上声明@BeforeAll和@AfterAll。因此,“per-class”模式还使得在@Nested测试类中使用@BeforeAll和@AfterAll方法成为可能。

但你可能已经读过了这些内容,并且正确地认为将字段声明为静态字段将产生与声明其为实例变量并使用 @TestInstance(Lifecycle.PER_CLASS) 相同的效果。

因此,也许回答“在JUnit 5中如何有用”的问题的答案是使用 @TestInstance ...

  • 表明你的意图。使用 static 关键字可能被认为是意外的,而使用 @TestInstance 则不太可能是意外或粗心的复制粘贴结果。
  • 将管理范围、生命周期和清理的责任委托给框架,而不必记住自己管理。

4
如果您想在嵌套测试类中使用带参数的MethodSource创建参数化测试,这也是必需的。参考链接:https://github.com/junit-team/junit5/issues/1229 - Nitesh
将测试类成员设置为静态可能会在类层次结构中出现问题。多态性为您提供了一些很酷的选项,可以编写更清洁、不冗余的代码。在我的看法中,@TestInstance(PER_CLASS) 应该是 Jupiter 中的默认设置。它可以产生更好、更具弹性的测试代码,并且与其他选择相比只有优势而无劣势。 - jannis

35

为了减少在运行单元测试时创建的对象数量,引入了这个注解。

@TestInstance(TestInstance.Lifecycle.PER_CLASS)添加到你的测试类中,可以避免为每个测试创建一个新的类实例。 当你在同一个测试类中有很多测试,并且实例化这个类是昂贵的时候,这个特性尤其有用。

应该谨慎使用这个注解。所有的单元测试应该是隔离的,彼此独立的。如果其中一个测试改变了测试类的状态,那么就不应该使用这个特性。

将字段设为静态以达到相同效果并不是一个好主意。虽然它确实减少了创建的对象数量,但当执行测试类中的所有测试时,无法清理这些对象。这可能会在你有一个庞大的测试套件时引发问题。


3
我喜欢这个答案,因为它直接解决了使用静态字段的一个主要弊端。 - Nathan Hughes
@dyVeloper 在TestNG中类似的注释是什么? - gomathi subramanian

15

@TestInstance 用于配置已注释的测试类或测试接口的测试实例生命周期:

  • PER_CLASS:每个测试类将创建一个新的测试实例。
  • PER_METHOD:每个测试方法、测试工厂方法或测试模板方法将创建一个新的测试实例。此模式类似于JUnit版本1到4中的行为。

如果未在测试类或由测试类实现的测试接口上显式声明@TestInstance,则生命周期模式会隐式默认为PER_METHOD


将测试实例的生命周期模式设置为 PER_CLASS 将启用以下功能:
  • 在给定测试类中,测试方法之间以及非静态@BeforeAll@AfterAll方法之间共享测试实例状态。
  • @Nested测试类中声明@BeforeAll@AfterAll方法。
  • 在接口默认方法上声明@BeforeAll@AfterAll
  • 在使用 Kotlin 编程语言实现的测试类中,简化声明@BeforeAll@AfterAll方法。
有关详细信息,请参见测试实例生命周期文档。

8

由于没有人提供适当的编码示例,因此我想提供以下简单的代码示例来理解该概念。

每种方法示例-Junit5中的默认选项请注意,两种方法都是静态的,否则会引发异常,因为每种方法都实例化类。

@TestInstance(Lifecycle.PER_METHOD)
public class MathUtilTestPerMethod {

    MathUtil util;

    @BeforeAll
    static void beforeAllInit() {
        System.out.println("running before all");
    }

    @AfterAll
    static void afterAllCleanUp() {
        System.out.println("running after all");
    }

    @BeforeEach
    void init() {
        util = new MathUtil();
        System.out.println("running before each...");
    }

    @AfterEach
    void cleanUp() {
        System.out.println("running after each...");
    }

    @Test
    void testSum() {
        assertEquals(2, util.addtwoNumbers(1, 1));
    }

}

每个类别的样本 请注意,这两个方法中都已经移除了 static 关键字,并且 MathUtil 对象在全局范围内创建,而不是在某个方法中创建,因为类只会实例化一次。

@TestInstance(Lifecycle.PER_CLASS)
public class MathUtilTestPerClass {

    MathUtil util = new MathUtil();

    @BeforeAll
    void beforeAllInit() {
        System.out.println("running before all");
    }

    @AfterAll
    void afterAllCleanUp() {
        System.out.println("running after all");
    }

    @BeforeEach
    void init() {
        System.out.println("running before each...");
    }

    @AfterEach
    void cleanUp() {
        System.out.println("running after each...");
    }

    @Test
    void testSum() {
        assertEquals(2, util.addtwoNumbers(1, 1));
    }

}

感谢您提供的有关在使用BeforeAll和AfterAll注释的方法中删除静态内容的提示。 - Askar

3

当在Kotlin中编写测试时(因为它没有静态方法),这也非常有用。

因此,不要使用带有@JvmStatic函数的伴生对象来进行@BeforeAll@AfterAll,而是将生命周期设置为PER_CLASS并使用@BeforeAll@AfterAll注释常规方法:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest {

    @BeforeAll
    fun setup() {
        println("I am invoked only once")
    }
}

此外,它还使得在参数化测试中可以使用简单的类方法作为@MethodSource的工厂函数(而不是将工厂函数包装在companion object {}中,并标记工厂函数为@JvmStatic)。
@ParameterizedTest
@MethodSource("generateStrings")
fun `Test strings`(argument: String) {
    check(argument.isNotEmpty())
}
private fun generateStrings() = listOf(
    "java",
    "kotlin"
)

使用这种方法时,请注意在必要时在@BeforeEach@AfterEach函数中重置实例变量。

感谢本文的帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接