Answers:
JUnit 总是为每个@Test方法创建一个测试类的实例。这是一个基本的设计决策,可以使编写测试变得更容易而没有副作用。好的测试没有任何运行顺序依赖关系(请参阅FIRST),为每个测试创建测试类及其实例变量的新实例对于实现这一点至关重要。一些测试框架将相同的测试类实例重用于所有测试,这导致了在测试之间意外产生副作用的更多可能性。
并且由于每个测试方法都有自己的实例,因此@ BeforeClass / @ AfterClass方法成为实例方法是没有意义的。否则,应在哪个测试类实例上调用这些方法?如果@ BeforeClass / @ AfterClass方法可以引用实例变量,则只有@Test方法之一可以访问这些相同的实例变量 -其余的实例变量将使用其默认值-而@测试方法将是随机选择的,因为.class文件中的方法顺序是未指定/依赖于编译器的(IIRC,Java的反射API以与.class文件中声明的顺序相同的顺序返回方法,尽管该行为也未指定-我已经写了一个图书馆 以按行号对它们进行实际排序)。
因此,将这些方法强制为静态是唯一合理的解决方案。
这是一个例子:
public class ExampleTest {
@BeforeClass
public static void beforeClass() {
System.out.println("beforeClass");
}
@AfterClass
public static void afterClass() {
System.out.println("afterClass");
}
@Before
public void before() {
System.out.println(this + "\tbefore");
}
@After
public void after() {
System.out.println(this + "\tafter");
}
@Test
public void test1() {
System.out.println(this + "\ttest1");
}
@Test
public void test2() {
System.out.println(this + "\ttest2");
}
@Test
public void test3() {
System.out.println(this + "\ttest3");
}
}
哪些打印:
beforeClass
ExampleTest@3358fd70 before
ExampleTest@3358fd70 test1
ExampleTest@3358fd70 after
ExampleTest@6293068a before
ExampleTest@6293068a test2
ExampleTest@6293068a after
ExampleTest@22928095 before
ExampleTest@22928095 test3
ExampleTest@22928095 after
afterClass
如您所见,每个测试都使用其自己的实例执行。JUnit所做的基本上与此相同:
ExampleTest.beforeClass();
ExampleTest t1 = new ExampleTest();
t1.before();
t1.test1();
t1.after();
ExampleTest t2 = new ExampleTest();
t2.before();
t2.test2();
t2.after();
ExampleTest t3 = new ExampleTest();
t3.before();
t3.test3();
t3.after();
ExampleTest.afterClass();
简短的答案是: 没有充分的理由使它保持静态。
实际上,如果您使用Junit执行基于DBUnit的DAO集成测试,则使其静态化会引起各种问题。静态需求会干扰依赖项注入,应用程序上下文访问,资源处理,日志记录以及任何依赖于“ getClass”的事物。
@PostConstruct
进行设置和@AfterClass
拆卸,而我完全忽略了Junit中的静态注释。对于DAO测试,我然后编写了自己的TestCaseDataLoader
类,该类从这些方法中调用。
@PostConstruct
和@AfterClass
刚刚的行为一样@Before
和@After
。实际上,将为每个测试方法调用您的方法,而不是为整个类调用一次(正如Esko Luontola在其回答中指出的那样,将为每个测试方法创建一个类的实例)。我看不到您的解决方案的实用程序(除非我错过了什么)
JUnit文档似乎很稀少,但我会猜测:也许JUnit在运行每个测试用例之前会创建一个新的测试类实例,因此,“ fixture”状态在运行期间持久存在的唯一方法是使其为静态,这可以通过确保您的fixtureSetup(@BeforeClass方法)是静态的来强制执行。
虽然这不能回答原始问题。它将回答明显的后续行动。如何创建在课程前后,测试前后均适用的规则。
为此,您可以使用以下模式:
@ClassRule
public static JPAConnection jpaConnection = JPAConnection.forUITest("my-persistence-unit");
@Rule
public JPAConnection.EntityManager entityManager = jpaConnection.getEntityManager();
在before(Class)上,JPAConnection在after(Class)上创建一次连接,然后将其关闭。
getEntityManger
返回一个内部类,该类JPAConnection
实现jpa的EntityManager并可以访问中的连接jpaConnection
。在(测试)之前,它在(测试)之后开始事务,然后再次回滚。
这不是线程安全的,但是可以做到这一点。
选定的代码 JPAConnection.class
package com.triodos.general.junit;
import com.triodos.log.Logger;
import org.jetbrains.annotations.NotNull;
import org.junit.rules.ExternalResource;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.metamodel.Metamodel;
import java.util.HashMap;
import java.util.Map;
import static com.google.common.base.Preconditions.checkState;
import static com.triodos.dbconn.DB2DriverManager.DRIVERNAME_TYPE4;
import static com.triodos.dbconn.UnitTestProperties.getDatabaseConnectionProperties;
import static com.triodos.dbconn.UnitTestProperties.getPassword;
import static com.triodos.dbconn.UnitTestProperties.getUsername;
import static java.lang.String.valueOf;
import static java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
public final class JPAConnectionExample extends ExternalResource {
private static final Logger LOG = Logger.getLogger(JPAConnectionExample.class);
@NotNull
public static JPAConnectionExample forUITest(String persistenceUnitName) {
return new JPAConnectionExample(persistenceUnitName)
.setManualEntityManager();
}
private final String persistenceUnitName;
private EntityManagerFactory entityManagerFactory;
private javax.persistence.EntityManager jpaEntityManager = null;
private EntityManager entityManager;
private JPAConnectionExample(String persistenceUnitName) {
this.persistenceUnitName = persistenceUnitName;
}
@NotNull
private JPAConnectionExample setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
return this;
}
@NotNull
private JPAConnectionExample setManualEntityManager() {
return setEntityManager(new RollBackAfterTestEntityManager());
}
@Override
protected void before() {
entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName, createEntityManagerProperties());
jpaEntityManager = entityManagerFactory.createEntityManager();
}
@Override
protected void after() {
if (jpaEntityManager.getTransaction().isActive()) {
jpaEntityManager.getTransaction().rollback();
}
if(jpaEntityManager.isOpen()) {
jpaEntityManager.close();
}
// Free for garbage collection as an instance
// of EntityManager may be assigned to a static variable
jpaEntityManager = null;
entityManagerFactory.close();
// Free for garbage collection as an instance
// of JPAConnection may be assigned to a static variable
entityManagerFactory = null;
}
private Map<String,String> createEntityManagerProperties(){
Map<String, String> properties = new HashMap<>();
properties.put("javax.persistence.jdbc.url", getDatabaseConnectionProperties().getURL());
properties.put("javax.persistence.jtaDataSource", null);
properties.put("hibernate.connection.isolation", valueOf(TRANSACTION_READ_UNCOMMITTED));
properties.put("hibernate.connection.username", getUsername());
properties.put("hibernate.connection.password", getPassword());
properties.put("hibernate.connection.driver_class", DRIVERNAME_TYPE4);
properties.put("org.hibernate.readOnly", valueOf(true));
return properties;
}
@NotNull
public EntityManager getEntityManager(){
checkState(entityManager != null);
return entityManager;
}
private final class RollBackAfterTestEntityManager extends EntityManager {
@Override
protected void before() throws Throwable {
super.before();
jpaEntityManager.getTransaction().begin();
}
@Override
protected void after() {
super.after();
if (jpaEntityManager.getTransaction().isActive()) {
jpaEntityManager.getTransaction().rollback();
}
}
}
public abstract class EntityManager extends ExternalResource implements javax.persistence.EntityManager {
@Override
protected void before() throws Throwable {
checkState(jpaEntityManager != null, "JPAConnection was not initialized. Is it a @ClassRule? Did the test runner invoke the rule?");
// Safety-close, if failed to close in setup
if (jpaEntityManager.getTransaction().isActive()) {
jpaEntityManager.getTransaction().rollback();
LOG.error("EntityManager encountered an open transaction at the start of a test. Transaction has been closed but should have been closed in the setup method");
}
}
@Override
protected void after() {
checkState(jpaEntityManager != null, "JPAConnection was not initialized. Is it a @ClassRule? Did the test runner invoke the rule?");
}
@Override
public final void persist(Object entity) {
jpaEntityManager.persist(entity);
}
@Override
public final <T> T merge(T entity) {
return jpaEntityManager.merge(entity);
}
@Override
public final void remove(Object entity) {
jpaEntityManager.remove(entity);
}
@Override
public final <T> T find(Class<T> entityClass, Object primaryKey) {
return jpaEntityManager.find(entityClass, primaryKey);
}
@Override
public final <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties) {
return jpaEntityManager.find(entityClass, primaryKey, properties);
}
@Override
public final <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode) {
return jpaEntityManager.find(entityClass, primaryKey, lockMode);
}
@Override
public final <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties) {
return jpaEntityManager.find(entityClass, primaryKey, lockMode, properties);
}
@Override
public final <T> T getReference(Class<T> entityClass, Object primaryKey) {
return jpaEntityManager.getReference(entityClass, primaryKey);
}
@Override
public final void flush() {
jpaEntityManager.flush();
}
@Override
public final void setFlushMode(FlushModeType flushMode) {
jpaEntityManager.setFlushMode(flushMode);
}
@Override
public final FlushModeType getFlushMode() {
return jpaEntityManager.getFlushMode();
}
@Override
public final void lock(Object entity, LockModeType lockMode) {
jpaEntityManager.lock(entity, lockMode);
}
@Override
public final void lock(Object entity, LockModeType lockMode, Map<String, Object> properties) {
jpaEntityManager.lock(entity, lockMode, properties);
}
@Override
public final void refresh(Object entity) {
jpaEntityManager.refresh(entity);
}
@Override
public final void refresh(Object entity, Map<String, Object> properties) {
jpaEntityManager.refresh(entity, properties);
}
@Override
public final void refresh(Object entity, LockModeType lockMode) {
jpaEntityManager.refresh(entity, lockMode);
}
@Override
public final void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties) {
jpaEntityManager.refresh(entity, lockMode, properties);
}
@Override
public final void clear() {
jpaEntityManager.clear();
}
@Override
public final void detach(Object entity) {
jpaEntityManager.detach(entity);
}
@Override
public final boolean contains(Object entity) {
return jpaEntityManager.contains(entity);
}
@Override
public final LockModeType getLockMode(Object entity) {
return jpaEntityManager.getLockMode(entity);
}
@Override
public final void setProperty(String propertyName, Object value) {
jpaEntityManager.setProperty(propertyName, value);
}
@Override
public final Map<String, Object> getProperties() {
return jpaEntityManager.getProperties();
}
@Override
public final Query createQuery(String qlString) {
return jpaEntityManager.createQuery(qlString);
}
@Override
public final <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery) {
return jpaEntityManager.createQuery(criteriaQuery);
}
@Override
public final <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass) {
return jpaEntityManager.createQuery(qlString, resultClass);
}
@Override
public final Query createNamedQuery(String name) {
return jpaEntityManager.createNamedQuery(name);
}
@Override
public final <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass) {
return jpaEntityManager.createNamedQuery(name, resultClass);
}
@Override
public final Query createNativeQuery(String sqlString) {
return jpaEntityManager.createNativeQuery(sqlString);
}
@Override
public final Query createNativeQuery(String sqlString, Class resultClass) {
return jpaEntityManager.createNativeQuery(sqlString, resultClass);
}
@Override
public final Query createNativeQuery(String sqlString, String resultSetMapping) {
return jpaEntityManager.createNativeQuery(sqlString, resultSetMapping);
}
@Override
public final void joinTransaction() {
jpaEntityManager.joinTransaction();
}
@Override
public final <T> T unwrap(Class<T> cls) {
return jpaEntityManager.unwrap(cls);
}
@Override
public final Object getDelegate() {
return jpaEntityManager.getDelegate();
}
@Override
public final void close() {
jpaEntityManager.close();
}
@Override
public final boolean isOpen() {
return jpaEntityManager.isOpen();
}
@Override
public final EntityTransaction getTransaction() {
return jpaEntityManager.getTransaction();
}
@Override
public final EntityManagerFactory getEntityManagerFactory() {
return jpaEntityManager.getEntityManagerFactory();
}
@Override
public final CriteriaBuilder getCriteriaBuilder() {
return jpaEntityManager.getCriteriaBuilder();
}
@Override
public final Metamodel getMetamodel() {
return jpaEntityManager.getMetamodel();
}
}
}
似乎JUnit为每个测试方法都创建了测试类的新实例。试试这个代码
public class TestJunit
{
int count = 0;
@Test
public void testInc1(){
System.out.println(count++);
}
@Test
public void testInc2(){
System.out.println(count++);
}
@Test
public void testInc3(){
System.out.println(count++);
}
}
输出为0 0 0
这意味着,如果@BeforeClass方法不是静态的,则必须在每个测试方法之前执行它,并且无法区分@Before和@BeforeClass的语义。
有两种类型的注释:
因此@BeforeClass必须声明为静态,因为它被调用了一次。您还应该考虑到静态是确保测试之间正确进行“状态”传播的唯一方法(JUnit模型为每个@Test施加一个测试实例),因为在Java中,只有静态方法才能访问静态数据... @BeforeClass和@ AfterClass只能应用于静态方法。
此示例测试应阐明@BeforeClass与@Before的用法:
public class OrderTest {
@BeforeClass
public static void beforeClass() {
System.out.println("before class");
}
@AfterClass
public static void afterClass() {
System.out.println("after class");
}
@Before
public void before() {
System.out.println("before");
}
@After
public void after() {
System.out.println("after");
}
@Test
public void test1() {
System.out.println("test 1");
}
@Test
public void test2() {
System.out.println("test 2");
}
}
输出:
-------------标准输出--------------- 课前 之前 测试1 后 之前 测试2 后 下课以后 ------------- ---------------- ---------------
按照JUnit 5,似乎已经放松了严格按照每种测试方法创建新实例的理念。他们添加了一个仅实例化一个测试类的注释。因此,此注释还允许使用@ BeforeAll / @ AfterAll注释的方法(@ BeforeClass / @ AfterClass的替换)是非静态的。因此,这样的测试类:
@TestInstance(Lifecycle.PER_CLASS)
class TestClass() {
Object object;
@BeforeAll
void beforeAll() {
object = new Object();
}
@Test
void testOne() {
System.out.println(object);
}
@Test
void testTwo() {
System.out.println(object);
}
}
将打印:
java.lang.Object@799d4f69
java.lang.Object@799d4f69
因此,每个测试类实际上可以实例化一次对象。当然,避免突变以这种方式实例化的对象确实是您的责任。
要解决此问题,只需更改方法
public void setUpBeforeClass
至
public static void setUpBeforeClass()
以及此方法中定义的所有内容static
。