JUNIT : run setup only once for a large number of

2019-02-16 05:45发布

I have a class, which I use as a basis for my unit tests. In this class I initialize the whole environment for my tests, setting up database mappings, enter a number of database records across multiple tables, etc. That class has a method with a @BeforeClass annotation which does the initialization. Next thing, I extend that class with specific classes in which I have @Test methods.

My question is, since the before class is exactly the same for all these test classes, how can I ensure that they are run only once for all the tests. One simple solution is that I could keep all the tests in one class. However, the number of tests is huge, also they are categorised based on functional heads. So they are located in different classes. However since they need the exact same setup, they inherit the @BeforeClass. As a result the whole setup is done at least once per test class, taking much more time in total than I would prefer.

I could, though, put them all in various subpackages under one package, hence if there is a way, how I can run set up once for all the tests within that package, it would be great.

6条回答
【Aperson】
2楼-- · 2019-02-16 06:20

JUnit doesn't support this, you will have to use the standard Java work-arounds for singletons: Move the common setup code into a static code block and then call an empty method in this class:

 static {
     ...init code here...
 }

 public static void init() {} // Empty method to trigger the execution of the block above

Make sure that all tests call init(), for example my putting it into a @BeforeClass method. Or put the static code block into a shared base class.

Alternatively, use a global variable:

 private static boolean initialize = true;
 public static void init() {
     if(!initialize) return;
     initialize = false;

     ...init code here...
 }
查看更多
Deceive 欺骗
3楼-- · 2019-02-16 06:20

Create one base class for all tests:

public class BaseTest {
    static{
        /*** init code here ***/
    }   
}

and every test should inherit from it:

public class SomeTest extends BaseTest {

}
查看更多
欢心
4楼-- · 2019-02-16 06:22

With JUnit4 test suite you can do something like this :

@RunWith(Suite.class)
@Suite.SuiteClasses({ Test1IT.class, Test2IT.class })
public class IntegrationTestSuite
{
    @BeforeClass
    public static void setUp()
    {
        System.out.println("Runs before all tests in the annotation above.");
    }

    @AfterClass
    public static void tearDown()
    {
        System.out.println("Runs after all tests in the annotation above.");
    }
}

Then you run this class as you would run a normal test class and it will run all of your tests.

查看更多
Evening l夕情丶
5楼-- · 2019-02-16 06:22

You can make one BaseTest class with a @BeforeClass method, then have all the other tests inherit from it. This way, when each test object is constructed, @BeforeClass gets executed.

Also avoid executing it just once for all the test suite, since all the test cases should be independent. @BeforeClass should execute only once each test case, not test suite.

查看更多
在下西门庆
6楼-- · 2019-02-16 06:25

If you can tolerate adding spring-test to your project, or you are using it already, then a good approach is to use the technique described here: How to load DBUnit test data once per case with Spring Test

查看更多
We Are One
7楼-- · 2019-02-16 06:27

Not sure if anyone still is using JUnit and trying to fix it without using Spring Runner (aka no spring integration). TestNG has this feature. But here is a JUnit based solution.

Create a RunOnce per thread operation like so. This maintains a list of classes for which the operation has run.

public class RunOnceOperation {
private static final ThreadLocal t = new ThreadLocal();

public void run(Function f) {
    if (t.get() == null) {
        t.set(Arrays.asList(getClass()));
        f.apply(0);
    } else {
        if (!((List) t.get()).contains(getClass())) {
            ((List) t.get()).add(getClass());
            f.apply(0);
        }
    }
  }
}

Back in your unit test

@Before
public beforeTest() {
    operation.run(new Function<Integer, Void>() {
        @Override
        public Void apply(Integer t) {
            checkBeanProperties();
            return null;
        }
    });
}

private void checkBeanProperties() {
   //I only want to check this once per class.
   //Also my bean check needs instance of the class and can't be static.
}


My function interface is like this:

interface Function<I,O> {
 O apply(I i); 
}

When you use this way, you can perform operations once per class using ThreadLocal.

查看更多
登录 后发表回答