Подтвердить что ты не робот

Почему jUnit fixtureSetup статичен?

Я отметил метод с аннотацией jUnit @BeforeClass и получил это исключение, сказав, что он должен быть статическим. Какое обоснование? Это заставляет все мои init быть на статических полях, по какой-либо веской причине, насколько я вижу.

В .Net(NUnit), это не тот случай.

Изменить - тот факт, что метод, аннотированный с помощью @BeforeClass, запускается только один раз, не имеет ничего общего с его статическим методом - можно использовать нестатический метод только один раз (как в NUnit).

4b9b3361

Ответ 1

JUnit всегда создает один экземпляр тестового класса для каждого метода @Test. Это принципиальное дизайнерское решение, чтобы упростить запись тестов без побочных эффектов. В хороших тестах нет зависимости от порядка выполнения (см. FIRST) и создания свежих экземпляров тестового класса и его переменных экземпляра для каждого теста имеет решающее значение для достижения этого. Некоторые структуры тестирования повторно используют один и тот же экземпляр тестового класса для всех тестов, что приводит к большему количеству возможностей случайного создания побочных эффектов между тестами.

И поскольку каждый тестовый метод имеет свой собственный экземпляр, для методов @BeforeClass/@AfterClass нет смысла быть методами экземпляра. В противном случае, на каком экземпляре тестового класса должны быть вызваны методы? Если бы методы @BeforeClass/@AfterClass могли бы ссылаться на переменные экземпляра, то только один из методов @Test имел бы доступ к тем же переменным экземпляра - остальное будет иметь переменные экземпляра по умолчанию по умолчанию, а значение @Метод тестирования был бы случайным образом выбран, так как порядок методов в файле .class не определен/зависит от компилятора (API IIRC, API отражения Java возвращает методы в том же порядке, в котором они объявлены в файле .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
[email protected]    before
[email protected]    test1
[email protected]    after
[email protected]    before
[email protected]    test2
[email protected]    after
[email protected]    before
[email protected]    test3
[email protected]    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();

Ответ 2

Короткий ответ таков: нет веской причины для его статичности.

На самом деле, статическая причина вызывает всевозможные проблемы, если вы используете Junit для выполнения тестов интеграции DAO на основе DBUnit. Статическое требование вмешивается в инъекцию зависимостей, доступ к контексту приложения, обработку ресурсов, ведение журнала и все, что зависит от "getClass".

Ответ 3

Документация JUnit кажется скудной, но я догадываюсь: возможно, JUnit создает новый экземпляр вашего тестового класса перед запуском каждого тестового примера, поэтому единственный способ сохранения состояния "fixture" в разных забегах - заставить его быть статическим, который можно выполнить, убедившись, что ваш fixtureSetup (метод @BeforeClass) является статическим.

Ответ 4

существует два типа аннотаций:

  • @BeforeClass (@AfterClass) , вызываемый один раз для каждого класса
  • @Before (и @After) вызывается перед каждым тестом

поэтому @BeforeClass должен быть объявлен static, потому что он вызывается один раз. Вы также должны учитывать, что статичность - единственный способ обеспечить правильное "состояние" распространения между тестами (модель JUnit накладывает один тестовый экземпляр на @Test), и поскольку в Java только статические методы могут обращаться к статическим данным... @BeforeClass и @AfterClass можно применять только к статическим методам.

В этом примере теста следует уточнить @BeforeClass vs @Before use:

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
------------- ---------------- ---------------

Ответ 5

Кажется, что 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

Ответ 6

Хотя это не ответит на исходный вопрос. Он ответит на очевидное продолжение. Как создать правило, которое работает до и после класса, и до и после теста.

Для этого вы можете использовать этот шаблон:

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

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

Вкл. (класс) JPAConnection создает соединение один раз после (Класс), он закрывает его.

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

Ответ 7

Согласно 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);
    }
}

будет печатать:

[email protected]
[email protected]

Таким образом, вы можете фактически создавать объекты один раз в тестовом классе. Конечно, это делает вашу собственную ответственность, чтобы избежать мутирования объектов, созданных таким образом.

Ответ 8

Чтобы решить эту проблему, просто измените метод

public void setUpBeforeClass 

to

public static void setUpBeforeClass()

и все, что определено в этом методе, на static.