Executing Initialization Code only once for multip

2019-06-28 04:27发布

问题:

I am writing unit test cases for my code. I am using PowerMockito with Junit. I have written an initialization code which will take care of all the initialization stuff in my application. Below is the way my code is structured:

Class ServiceInitializer{
    public static isInitialized = Boolean.FALSE;

    public static void initialize(){
        //Initilization Code Goes Here
        isInitialized = Boolean.TRUE;
    }
}

@RunWith(PowerMockRunner.class)
class BaseTest{

    @Before
    public void preTest(){
        //some code
    }


    static{
        if(!ServiceInitializer.isInitialized){
            ServiceInitializer.initialize();
        }
    }

    @After
    public void postTest(){
        //some code
    }
}

public TestClass1 extends BaseTest{
    @Test
    public void testMethodA_1(){//test code}

    @Test
    public void testMethodA_2(){//test code}

    @Test
    public void testMethodA_3(){//test code}
}

public TestClass2 extends BaseTest{
    @Test
    public void testMethodB_1(){//test code}

    @Test
    public void testMethodB_2(){//test code}

    @Test
    public void testMethodB_3(){//test code}
}

I am executing these test cases as ant script using Junit and batchtest targets as given below:

<junit printsummary="yes" haltonfailure="yes" showoutput="yes">
            <classpath refid="JUnitTesting.classpath"/>
            <batchtest haltonerror="false" haltonfailure="no" todir="${junit.output.dir}/raw">
                <formatter type="plain"/>
                <formatter type="xml"/>     
                <fileset dir="${basedir}/src">
                  <include name="**/Test*.java"/>
                </fileset>
            </batchtest>
        </junit>

I am able to execute the test cases just fine but its the initialization code, which is written in the static block of BaseTest class, which is causing problem for me. Whenever New Test Class starts its execution, "ServiceInitializer.initialize();" is getting called every time. So, say if I have 10 Test Classes then this method is getting called 10 times. I want to get control over it such that it only gets executed only once no matter how many Test Classes I have. How Can I achieve that? Is that even possible with JUnit?

--Update--

I know there is @BeforeClass annotation available which executes the block of code exactly once for each test class. But this is not fulfilling my requirements since as soon as JUnit runs the other test class it calls the method under "@BeforeClass" annotation and runs the initialization code again in spite of the initialization check there.

回答1:

You can use a class rule:

public class ServiceInitializer extends ExternalResource {
  public static final TestRule INSTANCE = new ServiceInitializer();
  private final AtomicBoolean started = new AtomicBoolean();

  @Override protected void before() throws Throwable {
    if (!started.compareAndSet(false, true)) {
      return;
    }
    // Initialization code goes here
  }

  @Override protected void after() {
  }
}

Then you can use this in your test by using the @ClassRule annotation:

@RunWith(PowerMockRunner.class)
class BaseTest{
  @Rule
  public PowerMockRule powerMockRule = new PowerMockRule();
  @ClassRule
  public static final TestRule serviceInitializer = ServiceInitializer.INSTANCE;

  @Before
  public final void preTest() {
    // some code
  }

  @After
  public final void postTest() {
    //some code
  }
}

Edit: PowerMockRunner apparently doesn't support @ClassRule, so I switched the test to use PowerMockRule. You can read more about it from here. I personally prefer rules over custom runners anyway.



回答2:

Consider to use the forkmode element in your ant-junit task. The default value is "perTest": It creates a new JVM for each test class. If you define the forkmode to be "perBatch" only one JVM is used for your batch. So your ServiceInitializer class stays initialized.

See also the help for junit task here



回答3:

You could use the @BeforeClass annotation in JUnit.

That will be executed once for the test class (BaseTest).

You shouldn't even need the 'public static isInitialized = Boolean.FALSE;'. Just trust JUnit. :)

To run before all test classes, you'll have to create a test suite and use @ClassRule in the test suite class.



回答4:

Try @BeforeClass to do the setup just once per class instead once before each test.



回答5:

PowerMockRunner installs its own classloader after having loaded your test class by the standard classloader. Better execute the static initalizers in a public static method which is annotated by

@BeforeClass

BTW: Unless you need a @PrepareForTest annotation at your test class level it's not necessary to run the test with PowerMockRunner.