为什么jUnit的fixtureSetup必须是静态的?

时间:2009-06-27 10:29:54

标签: java junit

我用jUnit的@BeforeClass注释标记了一个方法,并得到了这个异常,说它必须是静态的。理由是什么?这迫使我所有的init都在静态字段上,据我所知,没有任何理由。

在.Net(NUnit)中,情况并非如此。

编辑 - 使用@BeforeClass注释的方法只运行一次与静态方法无关 - 可以只运行一次非静态方法(如NUnit) )。

8 个答案:

答案 0 :(得分:117)

JUnit 始终为每个@Test方法创建一个测试类实例。 This is a fundamental design decision可以更轻松地编写没有副作用的测试。好的测试没有任何运行顺序依赖项(参见F.I.R.S.T),为每个测试创建测试类的新实例及其实例变量对于实现这一点至关重要。一些测试框架为所有测试重用相同的测试类实例,这导致在测试之间意外地产生副作用的更多可能性。

因为每个测试方法都有自己的实例,所以将@ BeforeClass / @ AfterClass方法作为实例方法是没有意义的。否则,应该在哪个测试类实例上调用这些方法?如果@ BeforeClass / @ AfterClass方法可以引用实例变量,那么只有一个@Test方法可以访问那些相同的实例变量 - 其余的将具有实例变量at它们的默认值 - 以及@Test方法将被随机选择,因为.class文件中的方法顺序是未指定的/编译器相关的(IIRC,Java的反射API以与它们中声明的顺序相同的顺序返回方法。类文件,虽然也没有指定该行为 - 我已经写了a library来实际按行号对它们进行排序。)

因此,强制执行这些方法是唯一合理的解决方案。

以下是一个例子:

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();

答案 1 :(得分:39)

简短的回答是这样的:没有充分的理由让它成为静态。

实际上,如果使用Junit执行基于DBUnit的DAO集成测试,将其设置为静态会导致各种问题。静态需求会干扰依赖注入,应用程序上下文访问,资源处理,日志记录以及依赖于“getClass”的任何内容。

答案 2 :(得分:12)

JUnit文档似乎很少,但我猜:在运行每个测试用例之前,JUnit可能会创建一个新的测试类实例,因此“fixture”状态在运行中保持持久的唯一方法是让它保持静态,可以通过确保您的fixtureSetup(@BeforeClass方法)是静态的来强制执行。

答案 3 :(得分:3)

虽然这不能回答原来的问题。它将回答明显的后续行动。如何创建在类之前和之后以及测试之前和之后工作的规则。

要实现这一点,您可以使用此模式:

@ClassRule
public static JPAConnection jpaConnection = JPAConnection.forUITest("my-persistence-unit");

@Rule
public JPAConnection.EntityManager entityManager = jpaConnection.getEntityManager();

在before(Class)之前,JPAConnection在(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();
    }
  }
}

答案 4 :(得分:2)

似乎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的语义

答案 5 :(得分:1)

有两种类型的注释:

  • @BeforeClass(@AfterClass)每个测试类调用一次
  • @Before(和@After)在每次测试前调用

所以@BeforeClass必须声明为static,因为它被调用一次。您还应该考虑静态是确保测试之间正确“状态”传播的唯一方法(JUnit模型为每个@Test强加一个测试实例),因为在Java中只有静态方法可以访问静态数据... @BeforeClass和@ AfterClass只能应用于静态方法。

此示例测试应阐明@BeforeClass vs @Before usage:

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");
    }
}

输出:

------------- Standard Output ---------------
before class
before
test 1
after
before
test 2
after
after class
------------- ---------------- ---------------

答案 6 :(得分:0)

根据JUnit 5,似乎严格按照测试方法创建新实例的理念有所松动。他们添加了an annotation,它只会实例化一次测试类。因此,这个注释还允许使用@ 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

因此,您实际上可以为每个测试类实例化一次对象。当然,这确实是你自己的责任,避免改变以这种方式实例化的对象。

答案 7 :(得分:-9)

要解决此问题,只需更改方法

即可
public void setUpBeforeClass 

public static void setUpBeforeClass()

以及此方法中定义的所有static