Test invocation: how to do set up common to all te

2019-02-17 09:34发布

问题:

Is there a way to know in a JUnit 4 test Class, if the Class was initiated by a Test-Suite ? I have global things that I want to run before all tests (regarding in-memory DB), so I thought doing it in the test-suit. However, I still want to be able to initiate one test at a time without a Test-Suit, So I need to know if I need to initialize the global things in the @Before part of the test... Does any-one know if it's possible ?

回答1:

There are several ways to achieve this. The easiest and simplest is to have a 'test' which is run at the beginning and end of your suite which set up your database and then set a global flag. In your @Before and @After tests you check this flag and if necessary do the setup/teardown.

@RunWith(Suite.class)
@SuiteClasses({SetupTest.class, RealTest.class, TeardownTest.class});

This is the simplest solution, but it isn't very nice, so a neater solution would be to use a TestRule. Look at extending ExternalResource. This implements before & after logic which surrounds your test methods. This would allow you to factor out your @Before and @After methods, to reuse the same code everywhere.

Then, for your suite, you need to implement before/after logic as well. Unfortunately, the class annotated with @RunWith(Suite.class) isn't actually instantiated, so you can't use the constructor of that class, but you can extend Suite. Depending upon how you will run this, you will will need to implement one of the constructors, using @RunWith as an example:

public class MySuite extends Suite {
    /**
     * Called reflectively on classes annotated with <code>@RunWith(Suite.class)</code>
     * 
     * @param klass the root class
     * @param builder builds runners for classes in the suite
     * @throws InitializationError
     */
    public MySuite(Class<?> klass, RunnerBuilder builder) throws InitializationError {
        this(builder, klass, getAnnotatedClasses(klass));
        // put your global setup here
        // set global variable
    }
}

Then run your test suite with

@RunWith(MySuite.class)

There are several constructors which are used in different situations, look at the comments next to each for specifics. You still need to use a global variable so that your Rules don't re-execute the setup code. The above will work if you're wanting to execute only setup code, executing teardown code is harder, but can be done. Let me know if you need it :-)

If you're wanting more flexibility (say executing setup code only for specific methods), then see my answer to How to define JUnit method rule in a suite?.



回答2:

I would use JUnit's @BeforeClass and @AfterClass annotations to indicate methods to do this type of work.

From the @BeforeClass Javadoc:

Sometimes several tests need to share computationally expensive setup (like logging into a database). While this can compromise the independence of tests, sometimes it is a necessary optimization. Annotating a public static void no-arg method with @BeforeClass causes it to be run once before any of the test methods in the class. The @BeforeClass methods of superclasses will be run before those the current class.



回答3:

You could place some @BeforeClass code in each test suite, which delegates to an auxiliary class that does the common set up. The auxiliary class could have a static boolean that records whether the set up has already been done. If it has already been done, the auxiliary class would do nothing.



标签: java junit4