@parameters method is executed before @beforeclass

2019-02-16 15:09发布

I'm using "parametrized" feature of junit 4 and I noticed that @parameters method is executed before @beforeclass method. This is creating a problem for me because the parameters i'm passing to the test cases via @parameters depends on the code initialize in the @beforeclass method. For example

@RunWith(Parameterized.class)
public class TestOtherClass {

    String argument;
    private static boolean initializeThis;

    public TestOtherClass(String parameter) throws Exception {
        argument=parameter;
    }

    @BeforeClass
    public static void doSetup() {
        System.out.println("Doing setup before class...");
        initializeThis=true; // true or false, based on some condition
    }

    @Test
    public void otherTest() {
        System.out.println("Other test: " + argument);
    }

    @Parameters
    public static Collection<Object[]> getData(){
        System.out.println("Inside parameter");
        String addThis;
        if(initializeThis)
            addThis="adding true";
        else
            addThis="adding false";

        Object[] para1 = new Object[]{"First parameter :: " + addThis};
        Object[] para2 = new Object[]{"Second parameter :: " + addThis};

        Collection<Object[]> classNames = new ArrayList<Object[]>();
        classNames.add(para1);
        classNames.add(para2);
        return classNames;
    }
}

Now, I'm initializing variable "initializeThis" to true in @beforeclass method but (surprisingly) when I executed the test case it prints

Other test: First parameter :: adding false
Other test: Second parameter :: adding false

That is something not expected.
My question is; is there any way to execute the @beforeclass method before @parameters, can we do this is in junit 4?

4条回答
Viruses.
2楼-- · 2019-02-16 15:50

This is old question but I had the same issue recently. It strikes me that none of the solutions seem to go for the most obvious workaround - calling the @BeforeClass method in the @Parameters method. The latter is static and executed only once - before any of the tests have been run. So, it is for all intents and purposes a @BeforeClass method even though it is not annotated as such. More details can be found here: http://feraldeveloper.blogspot.co.uk/2013/12/beforeclass-and-parametrized-junit-tests.html

查看更多
Rolldiameter
3楼-- · 2019-02-16 16:00

I would use just plain old java static {..} initializer instead of @BeforeClass, e.g.:

@RunWith(Parameterized.class)
public class TestOtherClass {

    String argument;
    private static boolean initializeThis;

    public TestOtherClass(String parameter) throws Exception {
        argument=parameter;
    }

    static {
        doSetup();
    }

    // @BeforeClass
    public static void doSetup() {
        System.out.println("Doing setup before class...");
        initializeThis=true; // true or false, based on some condition
    }

    @Test
    public void otherTest() {
        System.out.println("Other test: " + argument);
    }

    @Parameters
    public static Collection<Object[]> getData(){
        System.out.println("Inside parameter");
        String addThis;
        if(initializeThis)
            addThis="adding true";
        else
            addThis="adding false";

        Object[] para1 = new Object[]{"First parameter :: " + addThis};
        Object[] para2 = new Object[]{"Second parameter :: " + addThis};

        Collection<Object[]> classNames = new ArrayList<Object[]>();
        classNames.add(para1);
        classNames.add(para2);
        return classNames;
    }
}

Only drawback I know is that classes inherited from this won't be able to override static initializer, while @BeforeClass gives some freedom in this aspect;

查看更多
一纸荒年 Trace。
4楼-- · 2019-02-16 16:01

JUnit creates a Runner for each item in the parameter list, a Runner is what encapsulates the test method. So the @Parameters will always get executed before the @BeforeClass.

However, you can combine the @Parameterized with Assume. You always include all of the parameters in your list, whether or not you intend executing it. Then in the test method, add the assumeTrue() which tests against the initializeThis value.

@RunWith(Parameterized.class)
public class TestOtherClassAssume {
  private final String argument;
  private final boolean initializeThisTest;
  private static boolean initializeThis;

  @Parameters
  public static Collection<Object[]> getData(){
    System.out.println("Inside parameter");

    return Arrays.asList(new Object[][] {
      { false, "First" },
      { true, "Second" },
    });
  }

  public TestOtherClassAssume(boolean initializeThisTest, String argument) {
    this.initializeThisTest = initializeThisTest;
    this.argument = argument;
  }

  @BeforeClass
  public static void doSetup() {
    System.out.println("Doing setup before class...");
    initializeThis = true; // true or false, based on some condition
  }

  @Test
  public void otherTest() {
    Assume.assumeTrue(initializeThis == initializeThisTest);
    System.out.println("Other test: " + argument);
  }
}

The output from this is:

Inside parameter
Doing setup before class...
Other test: Second
查看更多
疯言疯语
5楼-- · 2019-02-16 16:01

However, this does not work with TestSuites. Given

@RunWith(Parameterized.class)
public class TogglableParameterizedTest {
    static boolean useAllParameters = false;

    int parameter;

    public TogglableParameterizedTest(int parameter) {
        super();
        this.parameter = parameter;
    }
    @Parameters
    public static Collection<Object[]> getTestParameters() {
        List<Object[]> parameters = new ArrayList<Object[]>();
        if(useAllParameters) {
            parameters.add(new Object[] { 1 });
            parameters.add(new Object[] { 2 });
            parameters.add(new Object[] { 3 });
        }
        else {
            parameters.add(new Object[] { 1 });
        }
        return parameters;
    }
    @Test
    public void test() {
        System.out.println("parameter=" + parameter);
    }
}

This does not work:

@RunWith(Suite.class)
@SuiteClasses({ TogglableParameterizedTest.class })
public class NonWorkingTestSuite1 {

    @BeforeClass
    public static void toggle() {
        System.out.println("sets flag to late!");
    }

}

The output is

sets flag to late!
parameter=1

Nor that:

@RunWith(Suite.class)
@SuiteClasses({ TogglableParameterizedTest.class }) 
public class NonWorkingTestSuite2 {
    static {
        System.out.println("sets flag still to late");
        TogglableParameterizedTest.useAllParameters = true;
    }
}

The output is "parameter=1". So the static initializer not executed at all. I found the following workaround. Extend "Suite" and insert the static initializer there:

public class TogglingSuite extends Suite {

    static {
        System.out.println("sets flag early enough!");
        TogglableParameterizedTest.useAllParameters = true;
    }

    public TogglingSuite(Class<?> klass, Class<?>[] suiteClasses)
        throws InitializationError {
        super(klass, suiteClasses);
    }

    public TogglingSuite(Class<?> klass, List<Runner> runners)
        throws InitializationError {
        super(klass, runners);
    }

    public TogglingSuite(Class<?> klass, RunnerBuilder builder)
            throws InitializationError {
        super(klass, builder);
    }

    public TogglingSuite(RunnerBuilder builder, Class<?> klass,
            Class<?>[] suiteClasses) throws InitializationError {
        super(builder, klass, suiteClasses);
    }

    public TogglingSuite(RunnerBuilder builder, Class<?>[] classes)
            throws InitializationError {
        super(builder, classes);
    }
}

and use it in your test suite:

@RunWith(TogglingSuite.class)
@SuiteClasses({ TogglableParameterizedTest.class })
public class WorkingTestSuite {

}

The output is

sets flag early enough!
parameter=1
parameter=2
parameter=3

Now it works.

查看更多
登录 后发表回答