JUnit:在调用每个@Test方法之前创建新实例。有什么好处?

28

目前,我正在阅读《JUnit实战》这本书。在这本书中,我发现以下文字:

在调用每个@Test方法之前,JUnit会创建测试类的一个新实例。这有助于提供测试方法之间的独立性,并避免测试代码中出现意外副作用。因为每个测试方法都在新的测试类实例上运行,所以我们不能跨测试方法重复使用实例变量值。

现在,我不太理解这种方法的意义:

例如:

public class CalculatorTest {
    @Test
    public void testAdd_1() {
        Calculator calculator = new Calculator();
        double result = calculator.add(1, 1);
        assertEquals(2, result, 0);
    }

    @Test
    public void testAdd_2() {
        Calculator calculator = new Calculator();
        double result = calculator.add(2, 2);
        assertEquals(4, result, 0);
    }
}

对于测试类CalculatorTest,没有任何好处。

好的,让我们关注另一个例子:

public class OneTest {

    static byte count;

    public OneTest() {
        count++;
    }

    @Test
    public void test1() {
        System.out.println(count);
    }

    @Test
    public void test2() {
        System.out.println(count);
    }
}

对于测试类OneTest,我发现了一种方法可以在许多测试方法中使用相同的变量count...

那么,如何看出书中描述的这种方法的真正好处呢?


1
如果你在谈论“count”字段,那么它是一个静态字段。它是类的属性,而不是特定实例的属性。这就是为什么你看到它的值会改变。 - baba smith
@babasmith:你应该恢复你的答案(也许可以通过展示一个非静态字段的示例来详细说明一下)。 - JB Nizet
第一个例子:我们的测试方法之间没有任何连接。在这种情况下,为每个测试方法创建新的测试类实例是没有意义的。第二个例子:我们可以通过静态变量获得“非预期副作用”。 :))这有什么好处呢? - user471011
2
当您在测试中使用实例变量并对其进行修改时,其好处就体现出来了。 - JB Nizet
目前这个问题在文档中有解释:https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-instance-lifecycle。 - vladr
3个回答

24
如何看到书中所描述的方法的真正好处?
分离实例的目的不是为了获得任何好处,而是为了保持每个测试应该独立执行,没有任何前一个测试执行的影响这一约定。除了为每个测试使用不同的实例,没有其他方法可以确保这个约定。
例如,Spring事务管理默认情况下保证回滚测试对数据库所做的所有更改,以维持相同的合同。
因此,在测试中使用静态变量通常是不鼓励的,因为它会破坏每个测试一个实例的整体目的,从而使每个测试具有干净的状态。

17

保持测试方法之间的状态干净对于单元测试非常有用,但对于功能测试来说则会妨碍其进行,因为在功能测试中通常需要在测试之间存在依赖关系(例如,当您使用Selenium测试网页时,如果登录页面的测试失败,则不运行某个页面的测试很有用)。

这是我创建TestNG的主要原因之一,它不会在每个方法之间实例化一个新类,因此可以让您选择而不是强制执行此决策。

TestNG还支持测试的依赖关系、多线程测试、具有组的概念(“仅运行servlet测试”)以及许多其他功能。


1
JUnit 5现在有@TestInstance(LifeCycle.PER_CLASS),这可能比切换到TestNG更容易。JUnit 5有一种奇怪的新命名约定,使其难以找到,因此这里是Maven Central的链接:https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine/ - Alex R

3
如果您正在测试一个可变的类,那么在每个测试方法开始时让您的测试对象处于已知状态非常有价值,这样测试执行顺序就不重要了。最简单的方法是为每个测试创建该类的新实例,并避免使用静态字段。
在您的计算器示例中,您的Calculator类似乎是不可变的,方法调用的结果仅取决于参数。因此,一个测试影响另一个测试的风险不存在。
我不太明白您第二个示例的意义。您编写了带有@Test注释的方法,使用了共享的静态字段,但是您的方法没有断言,也没有真正测试任何内容。
如果您想使用静态字段或者确实保留并重复使用被测试的类的单个实例,当然可以这样做,但是为了使您的测试工作并保持彼此独立,需要更加小心。

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