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

时间:2014-12-09 23:51:51

标签: java unit-testing junit junit4

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

由于以下几个原因,这对我来说似乎很尴尬:

  1. JUnit4的一部分(根据我的理解)是我们不再需要这种经典的测试继承。
  2. 当我将这些测试作为套件的一部分而不是单独运行(我们经常这样做)时,@BeforeClass@AfterClass会被多次调用,从而减慢测试速度 - 我们真的应该只是打电话给这些
  3. 我想要做的是以某种方式将当前的BeforeClass / AfterClass逻辑移出继承链,并将其转换为可以由各个测试和整个套件共享的内容。

    可以这样做吗?如果是这样,怎么做?(如果重要的话,我使用JUnit 4.7,更新到不同的版本可能很难卖出)

3 个答案:

答案 0 :(得分:29)

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

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,并且各个测试将仅重用已经过时的测试(引用计数使其不会被实际销毁并在套件中的测试之间重新创建)。

答案 1 :(得分:2)

您可以使用@BeforeClass@AfterClass 在套房类

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

这样你只能运行一次。

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

@BeforeClass
public static void setup(){

}

@AfterClass
public static void tearDown(){

}

}

答案 2 :(得分:1)

我遇到了类似的问题(Spring不是一个选项,我不会在maven项目中编写TestSuite)所以我写了简单的junit runner来解决这个问题。

您需要编写SharedResource类并将测试标记为需要该资源。

public class SampleSharedResource implements SharedResource {
public void initialize() throws Exception {
    //init your resource
}

}

@RunWith(JUnitSharedResourceRunner.class)
@JUnitSharedResourceRunner.WithSharedResources({SampleSharedResource.class})
public class SharedResourceRunnerATest {
...

来自https://github.com/eanlr/junit-shared-resources-runner

的来源