How to avoid inheritance in JUnit test cases?

2019-01-22 06:25发布

问题:

I have a number of test cases in JUnit. All of them need the same code to be executed in their @BeforeClass static method. It's a code duplication and I'm trying to get rid of it. A dirty way of doing this is by inheritance. Are there any other mechanisms in JUnit, that may help?

PS. I wrote this blog post about this very subject: http://www.yegor256.com/2015/05/25/unit-test-scaffolding.html

回答1:

The JUnit way to compose reusable code (instead of inheriting from it) are Rules.

See https://github.com/junit-team/junit/wiki/Rules

Here is a dumb sample, but you'll get the point.

import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
import org.junit.runner.Description;

public class MyTestRule implements TestRule {
  @Override
  public Statement apply(final Statement statement, Description description) {
    return new Statement() {
      public void evaluate() throws Throwable {
        // Here is BEFORE_CODE
        try {
          statement.evaluate();
        } finally {
          // Here is AFTER_CODE
        }
      }
    };
  }
}

You can then use your TestRule like this:

import org.junit.Rule;

public class MyTest {
    @Rule
    public MyTestRule myRule = new MyTestRule();
}

BEFORE_CODE and AFTER_CODE will then be executed around each of your test methods.

If you need to run your code only once per class, use your TestRule as a @ClassRule:

import org.junit.ClassRule;

public class MyTest {
    @ClassRule
    public static MyTestRule myRule = new MyTestRule();
}

Now, BEFORE_CODE and AFTER_CODE will be executed around each of your test class.

@Rule field is not static, @ClassRule field is.

A @ClassRule can be declared in a Suite too.

Note that you can declare several rules in a single test class, that's how you compose test lifecycles at test-suites, test-classes and test-methods levels.

A Rule is an object that you instanciate in your test classes (statically or not). You can add contructor parameters if needed.

HTH



回答2:

If the method is some kind of utility, then separate it out to a different class with a static method and call that method in your @BeforeClass.

I emphasize on the fact that don't use inheritance just because it solves your problem, use it when doing so creates sense in your class hierarchy.



回答3:

You may create test runner

public class MyTestRunner extends BlockJUnit4ClassRunner {
  @Override
  protected Object createTest() throws Exception {
     Object test = super.createTest();
     doStuff();
  }

  public void doStuff(){
     //common code
  }
}


@RunWith(MyTestRunner.class)
public class MyTest1{
    @Test
    public void test1(){
      //test method
    }
}


回答4:

Static methods aren't inherited, so inheritance isn't an option by default. If you mean you're moving the method to a common parent class, then that seems a poor choice since you only get one parent in Java. A test support class of some sort would seem more appropriate. It's also possible that you're seeing a need for a parameterized test.



回答5:

There is absolutely nothing wrong with inheritance in this case, it's actually the only way to avoid repeating this code in each subclass. The fact that @BeforeClass methods have to be declared static in JUnit is unfortunate, but that shouldn't stop you. Extend the class and you have the initialization code automatically run for you without having to do anything.



回答6:

If each and every class needs to have a @BeforeClass annotated method that is exactly the same as every other, then inheritance does not feel that wrong to me. If each of these initializing methods simply share some code, you could make a TestUtil class with some shared behavior and make calls to this shared behavior from each of the @BeforeClass methods.



回答7:

I think if the classes has "is-a" relation, inheritance is reasonable.

If the base class is MyBeforeClass which defines @BeforeClass method, and MyTestClass1 "is-a" MyBeforeClass, MyTestClass1 extends MyBeforeClass is OK.



回答8:

Depending on the nature of the setup code, you can potentially put all your tests in a test suite and have the setup code run there. The downside to this is that you cannot run tests individually (since the test depends on the setup code).



回答9:

It is test code, and it is not meant for heavy re-use. Do not over-engineer. Do not apply all the design patterns that you know. For test code, the rules are different.



标签: java junit