如何在多个测试类之间共享 JUnit BeforeClass 逻辑

2022-09-01 12:02:41

目前,我所有的JUnit测试都是从一个公共基类扩展而来的,该基类提供了标记和注释的方法 - 所有这些真正做的就是设置一堆静态资源/服务供测试使用。@BeforeClass@AfterClass

这对我来说似乎很尴尬,原因如下:

  1. JUnit4的部分观点(根据我的理解)是,我们不应该再需要这种经典的测试继承了。
  2. 当我将这些测试作为套件的一部分而不是单独运行(我们经常这样做)时,和 被多次调用,从而减慢了测试的速度 - 我们真的应该只调用这些测试一次@BeforeClass@AfterClass

我想做的是以某种方式将当前的BeforeClass/AfterClass逻辑移出继承链,并放入可以由各个测试和整个套件共享的内容中。

这能做到吗?如果是这样,如何?(如果这很重要,我正在使用JUnit 4.7,并且很难更新到其他版本)


答案 1

第一个问题的解决方案是通过 JUnit 4.9 中引入的 @ClassRule 将逻辑移动到连接到测试的扩展中:org.junit.rules.ExternalResource

public class MyTest {
    @ClassRule
    public static final TestResources res = new TestResources();
    @Test
    public void testFoo() {
        // test logic here
    }
}

public class TestResources extends ExternalResource {
    protected void before() {
        // Setup logic that used to be in @BeforeClass
    }
    protected void after() {
        // Setup logic that used to be in @AfterClass
    }
}

通过这种方式,以前由基类管理的资源将移出测试类层次结构,并移动到更模块化/可使用的“资源”中,这些资源可以在类运行之前创建,并在类运行后销毁。

至于同时解决这两个问题 - 即:将相同的高级设置/拆卸作为单个测试的一部分和套件的一部分运行 - 似乎没有任何特定的内置支持。但是...您可以自己实现它

只需将资源创建更改为工厂模式,该模式在内部引用计数以确定是否创建/销毁资源。@ClassRule

例如(请注意,这很粗糙,可能需要一些调整/错误处理才能实现稳健性):

public class TestResources extends ExternalResource {
    private static int refCount = 0;

    private static TestResources currentInstance;

    public static TestResources getTestResources () {
        if (refCount == 0) {
            // currentInstance either hasn't been created yet, or after was called on it - create a new one
            currentInstance = new TestResources();
        }
        return currentInstance;
    }

    private TestResources() {
        System.out.println("TestResources construction");
        // setup any instance vars
    }

    protected void before() {
        System.out.println("TestResources before");
        try {
            if (refCount == 0) {
                System.out.println("Do actual TestResources init");
            }
        }
        finally {
            refCount++;
        }
    }

    protected void after() {
        System.out.println("TestResources after");
        refCount--;
        if (refCount == 0) {
            System.out.println("Do actual TestResources destroy");
        }
    }
}

您的套件/测试类都将资源用作通过工厂方法:@ClassResource

@RunWith(Suite.class)
@SuiteClasses({FooTest.class, BarTest.class})
public class MySuite {
    @ClassRule
    public static TestResources res = TestResources.getTestResources();
    @BeforeClass
    public static void suiteSetup() {
        System.out.println("Suite setup");
    }
    @AfterClass
    public static void suiteTeardown() {
        System.out.println("Suite teardown");
    }
}
public class FooTest {
    @ClassRule
    public static TestResources res = TestResources.getTestResources();

    @Test
    public void testFoo() {
        System.out.println("testFoo");
    }
}
public class BarTest {
    @ClassRule
    public static TestResources res = TestResources.getTestResources();

    @Test
    public void testBar() {
        System.out.println("testBar");
    }
}

当运行单个测试时,重新计数不会产生任何影响 - “实际初始化”和“实际拆解”只会发生一次。在套件中运行时,套件将创建TestResource,并且各个测试将仅重用已经实例化的测试(引用计数可防止它在套件中的测试之间被实际销毁和重新创建)。


答案 2

您可以使用 和 在 套件 类 中。@BeforeClass@AfterClass

这将在套件中的任何测试类执行之前和所有测试类完成之后(分别)运行这些方法。

这样,您只能运行一次它们。

//..usual @RunWith etc annotations here
public class MySuite{

@BeforeClass
public static void setup(){

}

@AfterClass
public static void tearDown(){

}

}

推荐