Вопрос по java – Почему набор инструментов jUnit должен быть статическим?

103

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

В .Net (NUnit) это не так.

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

Ваш Ответ

8   ответов
3

продолжение. Как создать правило, которое работает до и после занятий, а также до и после теста.

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

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

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

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

getEntityManger возвращает внутренний классJPAConnection который реализует EntityManager jpa и может получить доступ к соединению внутри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();
    }
  }
}
38

there is no good reason for it to be static.

Фактически, его статичность вызывает всевозможные проблемы, если вы используете Junit для выполнения интеграционных тестов DAO на основе DBUnit. Статическое требование влияет на внедрение зависимостей, доступ к контексту приложения, обработку ресурсов, ведение журнала и все, что зависит от & quot; getClass & quot ;.

@HDave, я думаю, что ваше решение с@PostConstruct а также@AfterClass просто вести себя так же, как@Before а также@After , Фактически, ваши методы будут вызываться для каждого тестового метода, а не один раз для всего класса (как утверждает Эско Луонтола в своем ответе, экземпляр класса создается для каждого тестового метода). Я не могу увидеть полезность вашего решения, так (если я что-то упустил)
Конечно, у авторов JUnit была причина, я говорю, что это неgood причина ... таким образом, источник ОП (и 44 других людей) был мистифицирован. Было бы тривиально использовать методы экземпляра, и тестировщики должны использовать соглашение для их вызова. В конце концов, это то, что все делают, чтобы обойти это ограничение - либо бросьте своего собственного бегуна, либо свой собственный тестовый класс.
Это ужасный ответ, очевидно, что на самом деле есть причина для того, чтобы он был статичным, как четко указывает принятый ответ. Вы можете не согласиться с дизайнерским решением, но это далеко не означает, что для этого нет «веской причины». для решения.
Он работает правильно уже 5 лет, поэтому я думаю, что мое решение работает.
Я написал свой собственный суперкласс тестового примера и использую аннотации Spring@PostConstruct для настройки и@AfterClass за срыв, и я полностью игнорирую статические из Junit. Для тестов DAO я тогда написал свойTestCaseDataLoader класс, который я вызываю из этих методов.
116

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

И поскольку у каждого метода тестирования есть свой экземпляр, нет смысла для методов @ BeforeClass / @ AfterClass быть методами экземпляра. Иначе, в каком из экземпляров тестового класса должны быть вызваны методы? Если бы методы @ BeforeClass / @ AfterClass могли ссылаться на переменные экземпляра,then only one of the @Test methods would have access to those same instance variables - остальные будут иметь переменные экземпляра с их значениями по умолчанию - и метод @Test будет выбран случайным образом, потому что порядок методов в файле .class не определен / зависит от компилятора (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

Как видите, каждый из тестов выполняется со своим экземпляром. То, что делает , в основном так же, как это:

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 вызывается из тестового запуска ala Eclipse, или Spring Test, или Maven. В этих случаях создается один экземпляр тестового класса.
Хотя я понимаю проектное решение, я думаю, что оно не учитывает пользователей. потребности бизнеса. Поэтому, в конце концов, внутреннее решение о дизайне (о котором я не должен заботиться так же, как о пользователе, как только библиотека работает хорошо), вынуждает меня выбирать варианты дизайна в моих тестах, которые являются действительно плохими методами. Это на самом деле совсем не ловко: D
Нет, JUnit всегда создает множество экземпляров тестового класса, независимо от того, что мы использовали для запуска тестов. Только если у вас есть пользовательский Runner для тестового класса, может произойти что-то другое.
В этом примере он создалthree тестовые экземпляры. Здесь нетthe тестовый экземпляр.
& quot; Иначе, в каком из экземпляров тестового класса следует вызывать методы? & quot; - На тестовом экземпляре, созданном для запуска теста JUnit.
12

возможно, JUnit создает новый экземпляр вашего тестового класса перед запуском каждого тестового примера, так что единственный путь для вашего & quot; fixture & quot; Состояние, которое должно сохраняться при каждом прогоне, должно быть статичным, что можно обеспечить, убедившись, что ваш fixtureSetup (метод @BeforeClass) является статическим.

Создает ли TestNG один экземпляр класса test и делится им со всеми тестами в классе? Это делает его более уязвимым к побочным эффектам между тестами.
Это единственная причина, по которой они есть, но на самом деле бегун Junitcould выполнить работу методов BeforeTests и AfterTests так, как это делает testng.
Не только возможно, но JUnit определенно создает новый экземпляр контрольного примера. Так что это единственная причина.
-9

public void setUpBeforeClass 

в

public static void setUpBeforeClass()

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

Это не отвечает на вопрос вообще.
0

философия строгого создания нового экземпляра для каждого метода тестирования несколько ослабла. Они добавилианнотация это создаст экземпляр тестового класса только один раз. Поэтому эта аннотация также позволяет методам, аннотированным @ 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]

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

1

@BeforeClass (@AfterClass) called once per test class @Before (and @After) called before each test

поэтому @BeforeClass должен быть объявлен как static, потому что он вызывается один раз. Вам также следует учитывать, что быть статичным - это единственный способ обеспечить правильное «состояние». распространение между тестами (модель 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");
    }
}

выход:

------------- Standard Output ---------------
before class
before
test 1
after
before
test 2
after
after class
------------- ---------------- ---------------
Вы не можете с JUnit, извините. Вы должны использовать статическую переменную, ни в коем случае.
Я считаю ваш ответ неуместным. Я знаю семантику BeforeClass и Before. Это не объясняет, почему оно должно быть статичным ... ripper234
& quot; Это вынуждает всех моих инициаторов быть на статических элементах, насколько я вижу, без веской причины. & quot; Мой ответ должен показать вам, что ваш инициатор также может бытьnon-static используя @Before вместо @BeforeClass
Я хотел бы выполнить инициализацию только один раз, в начале класса, но с нестатическими переменными. ripper234
Если инициализация стоит дорого, вы можете просто сохранить переменную состояния, чтобы записать, выполнили ли вы инициализацию, и (проверить это и при желании) выполнить инициализацию в методе @Before ...
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++);
    }
}

The output is 0 0 0

Это означает, что если метод @BeforeClass не является статическим, то он должен выполняться перед каждым тестовым методом, и не будет никакого способа провести различие между семантикой @Before и @BeforeClass

Это не простоseem таким образом, этоis сюда. Вопрос задавался много лет, вот ответ:martinfowler.com/bliki/JunitNewInstance.html

Похожие вопросы