类之前的 Junit (非静态)

有没有什么最佳实践可以让 Junit 在一个测试文件中执行一次函数,而且它也不应该是静态的。

@BeforeClass的非静态功能?

这里有一个丑陋的解决方案:

@Before void init(){
if (init.get() == false){
init.set(true);
// do once block
}
}

这是我不想做的事情,我正在寻找一个集成的联合解决方案。

67985 次浏览

I've never tried but maybe you can create a no-argument constructor and call you function from there?

To use an empty constructor is the easiest solution. You can still override the constructor in the extended class.

But it's not optimal with all the inheritance. That's why JUnit 4 uses annotations instead.

Another option is to create a helper method in a factory/util class and let that method do the work.

If you're using Spring, you should consider using the @TestExecutionListeners annotation. Something like this test:

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({CustomTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class})
@ContextConfiguration("test-config.xml")
public class DemoTest {

Spring's AbstractTestExecutionListener contains for example this empty method that you can override:

public void beforeTestClass(TestContext testContext) throws Exception {
/* no-op */
}

NOTE: DO NOT overlook/miss DependencyInjectionTestExecutionListener while adding custom TestExecutionListeners. If you do, all the autowires will be null.

If you don't want to set up static initializers for one time initialization and are not particular about using JUnit, take a look at TestNG. TestNG supports non-static, one-time initialization with a variety of configuration options, all using annotations.

In TestNG, this would be equivalent to:

@org.testng.annotations.BeforeClass
public void setUpOnce() {
// One time initialization.
}

For teardown,

@org.testng.annotations.AfterClass
public void tearDownOnce() {
// One time tear down.
}

For the TestNG equivalent of JUnit 4's @Before and @After, you can use @BeforeMethod and @AfterMethod respectively.

A simple if statement seems to work pretty well too:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:test-context.xml"})
public class myTest {


public static boolean dbInit = false;


@Autowired
DbUtils dbUtils;


@Before
public void setUp(){


if(!dbInit){


dbUtils.dropTables();
dbUtils.createTables();
dbInit = true;


}
}


...

UPDATE: Please see the comment by Cherry for why the suggestion below is flawed. (Am keeping the answer on here rather than deleting as the comment may provide useful information to others as to why this doesn't work.)


Another option worth considering if using dependency injection (e.g. Spring) is @PostConstruct. This will guarantee dependency injection is complete, which wouldn't be the case in a constructor:

@PostConstruct
public void init() {
// One-time initialization...
}

The article discuss 2 very nice solutions for this problem:

  1. "clean" junit with custom Runner (using interface but you could extend it with a custom annotation e.g. @BeforeInstance)
  2. Spring execution listeners as mentioned by Espen before.

Just use @BeforeClass:

@BeforeClass
public static void init() {
}

It doesn't make sense for init to be non-static because each test is run in a separate instance. The instance that init is run on would not match the instance of any test.

The only reason that you might want it to be non-static is to override it in subclasses, but you can do this with static methods too. Just use the same name, and only the subclass init method will be called.

Easily use @BeforeAllMethods/@AfterAllMethods annotations to run a method inside the instance context (non-static), where all injected values will be available.

There is a special testing library for this:

https://mvnrepository.com/artifact/org.bitbucket.radistao.test/before-after-spring-test-runner/0.1.0

https://bitbucket.org/radistao/before-after-spring-test-runner/

The only limitation: works only for Spring testing.

(I'm the developer of this testing library)