How to share JUnit BeforeClass logic among multiple test classes

后端 未结 3 1252
感情败类
感情败类 2021-01-31 17:13

Currently, all of my JUnit tests extend from a common base class that provides methods tagged with @BeforeClass and @AfterClass annotations - all thes

相关标签:
3条回答
  • 2021-01-31 17:38

    You can use the @BeforeClass and @AfterClass IN THE SUITE CLASS.

    This will run the methods before any of the test classes in the suite execute and after all the test classes finish (respectively)

    This way you can run them only once.

    //..usual @RunWith etc annotations here
    public class MySuite{
    
    @BeforeClass
    public static void setup(){
    
    }
    
    @AfterClass
    public static void tearDown(){
    
    }
    
    }
    
    0 讨论(0)
  • 2021-01-31 17:45

    I came across similar problem (Spring was not an option and i don't write TestSuites in maven projects) so i wrote simple junit runner to solve this problem.

    You need to write SharedResource class and mark your test to require that resource.

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

    }

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

    Sources at https://github.com/eanlr/junit-shared-resources-runner

    0 讨论(0)
  • 2021-01-31 17:53

    A solution to the first issue is to move the logic into an extension of org.junit.rules.ExternalResource hooked up to the test via a @ClassRule, introduced in 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
        }
    }
    

    In this way, the resources previously managed by the base class are moved out of the test class hierarchy and into more modular/consumable "resources" that can be created before a class runs and destroyed after a class runs.

    As for solving both issues at the same time though - ie: having the same high level setup/teardown run as both part of an individual test and as part of a suite - there doesn't seem to be any specific built in support for this. However..., you could implement it yourself:

    Simply change the @ClassRule resource creation into a factory pattern that does reference counting internally to determine whether or not to create/destroy the resource.

    For example (please note this is rough and might need some tweaks/error handling for robustness):

    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");
            }
        }
    }
    

    Both your suite / test classes would just use the resource as a @ClassResource through the factory method:

    @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");
        }
    }
    

    When running an individual test, the refcounting won't have any effect - the "actual init" and "actual teardown" will only happen once. When running through the suite, the suite will create the TestResource, and the individual tests will just reuse the already instantated one (the refcounting keeps it from being actually destroyed and recreated between tests in the suite).

    0 讨论(0)
提交回复
热议问题