回答:
JUnitは常に、@ Testメソッドごとにテストクラスのインスタンスを1つ作成します。これは、副作用のないテストを作成しやすくするための基本的な設計上の決定です。優れたテストには実行順序の依存関係はなく(FIRSTを参照)、テストを実行するためにテストクラスの新しいインスタンスとそのインスタンス変数を作成することは、これを達成するために重要です。一部のテストフレームワークは、すべてのテストで同じテストクラスインスタンスを再利用するため、テスト間で誤って副作用が発生する可能性が高くなります。
また、各テストメソッドには独自のインスタンスがあるため、@ BeforeClass / @ AfterClassメソッドがインスタンスメソッドであっても意味がありません。それ以外の場合、メソッドを呼び出す必要があるテストクラスインスタンスはどれですか。@ BeforeClass / @ AfterClassメソッドがインスタンス変数を参照できる場合、@ Testメソッドの1つだけが同じインスタンス変数にアクセスでき、残りはデフォルト値のインスタンス変数を持ち、@ .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
破棄にSpringアノテーションを使用し、Junitからの静的アノテーションをすべて無視します。DAOテストでは、TestCaseDataLoader
これらのメソッドから呼び出す独自のクラスを作成しました。
@PostConstruct
と@AfterClass
ちょうど同じ振る舞い@Before
と@After
。実際、メソッドはクラス全体ではなく、テストメソッドごとに呼び出されます(Esko Luontolaが彼の回答で述べているように、クラスのインスタンスは各テストメソッドに対して作成されます)。私はあなたの解決策の有用性が見えないので(私が何かを
JUnitのドキュメントは不足しているようですが、おそらくJUnitは各テストケースを実行する前にテストクラスの新しいインスタンスを作成するので、「フィクスチャ」の状態を実行間で持続させる唯一の方法は、それを静的にすることです。あなたの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のセマンティクスを区別する方法はありません。
注釈には2つのタイプがあります。
したがって、@ BeforeClassは一度呼び出されるため、静的に宣言する必要があります。また、静的であることはテスト間の適切な「状態」伝播を保証する唯一の方法であることを考慮する必要があります(JUnitモデルは@Testごとに1つのテストインスタンスを課します)。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
したがって、実際には、テストクラスごとに1回オブジェクトをインスタンス化できます。もちろん、この方法でインスタンス化されたオブジェクトの変更を回避することは、ユーザー自身の責任になります。
この問題を解決するには、メソッドを変更するだけです
public void setUpBeforeClass
に
public static void setUpBeforeClass()
そして、すべてこの方法で定義されていますstatic
。