【问题标题】:How to share JUnit BeforeClass logic among multiple test classes如何在多个测试类之间共享 JUnit BeforeClass 逻辑
【发布时间】:2014-12-09 23:51:50
【问题描述】:

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

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

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

我想做的是以某种方式将当前的 BeforeClass/AfterClass 逻辑从继承链中移出,并转移到可以由单个测试和整个套件共享的东西中。

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

【问题讨论】:

    标签: java unit-testing junit junit4


    【解决方案1】:

    第一个问题的解决方案是将逻辑移动到通过 @ClassRule 连接到测试的 org.junit.rules.ExternalResource 的扩展中,在 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,而各个测试将仅重用已实例化的测试资源(引用计数可防止它在套件中的测试之间被实际销毁和重新创建)。

    【讨论】:

    • 我认为您的 refCount 系统不起作用。它会在每节课后归零并删除你的资源,然后在调用 getTestResources() 时在下一节课上重新初始化它。我可能遗漏了什么,如果我错了,请纠正我。
    • 这个想法是它可以在运行单个测试或一组测试时进行初始化。 作为套件运行时,套件ClassRule 将refCount 设置为1。每个测试都会将 refCount 更改为 2,然后在完成后返回 1。套件完成后,它将变为 0。作为测试运行时,单个测试将 refCount 设置为 1,然后在测试完成时设置为 0。关键是资源在两种运行方式中只创建和销毁一次。
    【解决方案2】:

    您可以使用@BeforeClass@AfterClass 在 SUITE 类中

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

    这样你只能运行一次。

    //..usual @RunWith etc annotations here
    public class MySuite{
    
    @BeforeClass
    public static void setup(){
    
    }
    
    @AfterClass
    public static void tearDown(){
    
    }
    
    }
    

    【讨论】:

    • 虽然如果我只从一个套件类中运行测试这很好,但当我单独运行测试时它不会工作(即:在 Eclipse 中,右键单击测试并选择“运行as JUnit test") - 套件知道测试,但测试不知道套件...
    【解决方案3】:

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

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

    public class SampleSharedResource implements SharedResource {
        public void initialize() throws Exception {
        //initialize your resource
        }
    }
    
    @RunWith(JUnitSharedResourceRunner.class)
    @JUnitSharedResourceRunner.WithSharedResources({SampleSharedResource.class})
    public class SharedResourceRunnerATest {
        ...
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-03-01
      • 2013-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-23
      • 1970-01-01
      相关资源
      最近更新 更多