7

DAOレイヤーをテストする適切な方法は何ですか?

作成および破棄したメソッドに注釈@BeforeMethodを付けましたが、複数のテストでは機能しませんでした。テストは 1 つずつ実行すると成功しましたが、Maven ビルドではすべて一緒に実行すると成功しなかったため、TestNg グループを使用してテストをグループ化し、Hibernate で同じことを行ったメソッドを実行することにしました。@AfterMethodSessionFactory@BeforeGroup@AfterGroup

だから私はこのようなことをしました:

@Test(groups = {"integration"})
public class IntegrationTest
{
    protected SessionFactory sessionFactory;

    @BeforeGroups(groups = {"integration"})
    public void setUpHibernate() throws Exception
    {
        // here I configure sessionFactory
        this.sessionFactory = ...
    }

    @AfterGroups(groups = {"integration"})
    public void putItDown() throws Exception
    {
        sessionFactory.close();
    }
}

そして、私の各テストはこのクラスを次のように拡張しました

@Test(groups = "integration")
public class RateRepositoryHibernateTest  extends IntegrationTest
{
    ...
}

そして、拡張テストが 1 つしかsessionFactory設定されておらず、残りはnull驚くことではありませんでした。今、私は本当に何をすべきかわかりません。

@BeforeGroupグループテストメソッドのメソッドからデータを渡す方法は?

また

どうやってそれを違う方法で行うのですか?

また

SessionFactory各テストの前後にsetUpとtearDownを行う方法ですが、複数のテストで悲観的なロック例外が発生しないようにする方法は?

- 編集:

別の方法を説明する回答も大歓迎です。TestNg、Hibernate、およびメモリ内データベースを使用した統合テストの最先端を知りたいです。

-- 編集 2:

スタック トレース、以下のテストのコード

Hibernate: select roomtype_.type_name from ROOM_TYPES roomtype_ where roomtype_.type_name=?
Hibernate: call next value for rates_sequence
Hibernate: call next value for rates_sequence
Hibernate: insert into ROOMS (prefix, housekeepingStatus, availability, maxExtraBeds, standard, maximum, type, name) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into RATES (standardPrice, upchargeExtraPerson, upchargeExtraBed, room, RATE_TYPE, id) values (?, ?, ?, ?, 'R', ?)
Hibernate: insert into RATES (standardPrice, upchargeExtraPerson, upchargeExtraBed, room, seasonId, RATE_TYPE, id) values (?, ?, ?, ?, ?, 'S', ?)
Hibernate: insert into ROOM_TYPES (type_name) values (?)
Hibernate: select roomtype_.type_name from ROOM_TYPES roomtype_ where roomtype_.type_name=?
Hibernate: call next value for rates_sequence
Hibernate: insert into ROOMS (prefix, housekeepingStatus, availability, maxExtraBeds, standard, maximum, type, name) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into RATES (standardPrice, upchargeExtraPerson, upchargeExtraBed, room, RATE_TYPE, id) values (?, ?, ?, ?, 'R', ?)
org.hibernate.PessimisticLockException: Timeout trying to lock table ; SQL statement:
insert into RATES (standardPrice, upchargeExtraPerson, upchargeExtraBed, room, RATE_TYPE, id) values (?, ?, ?, ?, 'R', ?) [50200-168]
    at org.hibernate.dialect.H2Dialect$2.convert(H2Dialect.java:317)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
    at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:129)
    at org.hibernate.engine.jdbc.internal.proxy.AbstractProxyHandler.invoke(AbstractProxyHandler.java:81)
    at $Proxy13.executeUpdate(Unknown Source)
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:56)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2962)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3403)
    at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:88)
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:362)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:354)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:275)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:326)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:52)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1210)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:399)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)
    at net.mklew.hotelms.persistance.RatesPersistanceTest.should_save_rates_and_retrieve_them_with_success(RatesPersistanceTest.java:80)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:673)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:842)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1166)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
    at org.testng.TestRunner.runWorkers(TestRunner.java:1178)
    at org.testng.TestRunner.privateRun(TestRunner.java:757)
    at org.testng.TestRunner.run(TestRunner.java:608)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
    at org.testng.SuiteRunner.run(SuiteRunner.java:240)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1158)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1083)
    at org.testng.TestNG.run(TestNG.java:999)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:203)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:174)
    at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:111)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: org.h2.jdbc.JdbcSQLException: Timeout trying to lock table ; SQL statement:
insert into RATES (standardPrice, upchargeExtraPerson, upchargeExtraBed, room, RATE_TYPE, id) values (?, ?, ?, ?, 'R', ?) [50200-168]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:329)
    at org.h2.message.DbException.get(DbException.java:158)
    at org.h2.command.Command.filterConcurrentUpdate(Command.java:276)
    at org.h2.command.Command.executeUpdate(Command.java:232)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:156)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:142)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:122)
    ... 47 more
Caused by: org.h2.jdbc.JdbcSQLException: Concurrent update in table "PRIMARY_KEY_4": another transaction has updated or deleted the same row [90131-168]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:329)
    at org.h2.message.DbException.get(DbException.java:169)
    at org.h2.message.DbException.get(DbException.java:146)
    at org.h2.table.RegularTable.addRow(RegularTable.java:146)
    at org.h2.command.dml.Insert.insertRows(Insert.java:124)
    at org.h2.command.dml.Insert.update(Insert.java:84)
    at org.h2.command.CommandContainer.update(CommandContainer.java:75)
    at org.h2.command.Command.executeUpdate(Command.java:230)
    ... 54 more

テスト用のコード:

public class RatesPersistanceTest
{

    protected SessionFactory sessionFactory;
    protected HibernateSessionFactory hibernateSessionFactory;

    @BeforeMethod
    public void setUpHibernate() throws Exception
    {
        Logger logger = mock(Logger.class);
        NativelyConfiguredHibernateSessionFactory hibernateSessionFactory = new
                NativelyConfiguredHibernateSessionFactory(logger);
        this.sessionFactory = hibernateSessionFactory.getSessionFactory();
        this.hibernateSessionFactory = hibernateSessionFactory;
    }

    @AfterMethod
    public void putItDown() throws Exception
    {
        sessionFactory.close();
    }

    @Test
    public void should_save_rates_and_retrieve_them_with_success()
    {
        // given
        Money standardPrice = Money.parse("USD 85");
        Money upchargeExtraPerson = Money.parse("USD 80");
        Money upchargeExtraBed = Money.parse("USD 75");
        RoomType roomType = getMeRoomType();
        Room room = getMeRoom(roomType);

        AvailabilityPeriod availabilityPeriod = new AvailabilityPeriod(DateTime.now(), DateTime.now().plusDays(5),
                true);
        Season season = new BasicSeason("season name", availabilityPeriod);
        Rate seasonRate = new SeasonRate(standardPrice, upchargeExtraPerson, upchargeExtraBed, room, season);

        Session session = sessionFactory.openSession();
        session.beginTransaction();

        session.save(roomType);
//        session.save(room);

        session.getTransaction().commit();
        session.close();


        session = sessionFactory.openSession();
        session.beginTransaction();

        //        session.save(roomType);
        session.save(room);

        session.getTransaction().commit();
        session.close();

        session = sessionFactory.openSession();
        session.beginTransaction();
        session.save(season);

        session.save(seasonRate);

        session.getTransaction().commit();
        session.close();
        // when
        session = sessionFactory.openSession();
        session.beginTransaction();
        final List<Rate> list = session.createQuery("from Rate").list();

        // then
        assertThat(list).contains(seasonRate);
        session.getTransaction().commit();
        session.close();

    }

    @Test( expectedExceptions = org.hibernate.exception.ConstraintViolationException.class)
    public void season_rate_should_violate_db_constraints_when_saved_without_season()
    {
        // given
        Money standardPrice = Money.parse("USD 85");
        Money upchargeExtraPerson = Money.parse("USD 80");
        Money upchargeExtraBed = Money.parse("USD 75");
        RoomType roomType = getMeRoomType();
        final RoomName roomName = new RoomName("103");

        final Money roomStandardPrice = Money.parse("USD 100");
        final Money roomUpchargeExtraPerson = Money.parse("USD 50");
        final Money roomUpchargeExtraBed = Money.parse("USD 20");
        final RackRate rackRate = new RackRate(roomStandardPrice, roomUpchargeExtraPerson, roomUpchargeExtraBed, null);
        final int maxExtraBeds = 2;
        final Occupancy occupancy = new Occupancy(4, 2);
        Room room = new Room("C", roomName, roomType, HousekeepingStatus.CLEAN, RoomAvailability.AVAILABLE,
                maxExtraBeds, occupancy, standardPrice, upchargeExtraPerson, upchargeExtraBed);


        AvailabilityPeriod availabilityPeriod = new AvailabilityPeriod(DateTime.now(), DateTime.now().plusDays(5),
                true);
        Season season = new BasicSeason("season name", availabilityPeriod);
        Rate seasonRate = new SeasonRate(standardPrice, upchargeExtraPerson, upchargeExtraBed, room, null);

        Session session = sessionFactory.openSession();
        session.beginTransaction();

        session.save(room);
        // when
        session.save(seasonRate);

        session.getTransaction().commit();
        session.close();
        // then exception should be thrown
    }

//    @Test
//    public void package_rate_should_violate_db_constraints_when_saved_without_package()
//    {
//
//    }

    private RoomType getMeRoomType()
    {
        final RoomType roomType = new RoomType("cheap" + DateTime.now().toString());
        return roomType;
    }

    private Room getMeRoom(RoomType roomType)
    {
        final RoomName roomName = new RoomName("101001" + DateTime.now().toString());

        final Money standardPrice = Money.parse("USD 100");
        final Money upchargeExtraPerson = Money.parse("USD 50");
        final Money upchargeExtraBed = Money.parse("USD 20");
        final RackRate rackRate = new RackRate(standardPrice, upchargeExtraPerson, upchargeExtraBed, null);
        final int maxExtraBeds = 2;
        final Occupancy occupancy = new Occupancy(4, 2);
        return new Room("C", roomName, roomType, HousekeepingStatus.CLEAN, RoomAvailability.AVAILABLE, maxExtraBeds,
                occupancy, standardPrice, upchargeExtraPerson, upchargeExtraBed);
    }

}

休止状態の cfg:

<hibernate-configuration>

    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">org.h2.Driver</property>
        <property name="connection.url">jdbc:h2:mem:db1;DB_CLOSE_DELAY=0;MVCC=true</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"/>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.H2Dialect</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCachingRegionFactory</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">create-drop</property>

       <!-- skipped mappings -->

    </session-factory>

</hibernate-configuration>
4

2 に答える 2

3

Hibernate を使用するコードをテストするための私の好ましい方法は、アプローチを使用@BeforeMethodしてテストすることです。@AfterMethod私は TestNg の経験はありませんが、これらの関数を処理する方法は、JUnit3.8.1 が setUp() と TeaDown() を使用して行う方法と似ていると思います。

私にとって重要な点は、単体テストの実行順序は重要ではないという考えです。と を使用している@BeforeGroup場合@AfterGroup、同じSessionFactoryインスタンス、したがって同じデータベース (メモリ内かどうかにかかわらず) があり、テストが実行される変更操作は、SessionFactory読み取り操作を行う後のテストに影響します。それが望ましいかもしれませんが(順序はおそらく予測可能である必要があります)、その場合、実際には単一の「統合テスト」について話しているのですが、あなたの場合は、テストを独立させたいようです。

次の質問は、それをどのように行うかです。私がやっていることは、Hibernate を構成してメモリ内データベースを作成して接続し、その上でスキーマ作成スクリプトを実行し、テスト メソッドごとに SessionFactory を作成することです。

@Override
protected void setUp() throws Exception {
    super.setUp();
    String dialectClassName = HSQLDialect.class.getName();
    AnnotationConfiguration config = new AnnotationConfiguration().addAnnotatedClass(DividendScheduleGeneratorImpl.class);

    config.setProperty(Environment.DIALECT, dialectClassName);
    config.setProperty(Environment.DRIVER, jdbcDriver.class.getName());
    config.setProperty(Environment.URL, "jdbc:hsqldb:mem:testDB");
    config.setProperty(Environment.USER, "SA");
    config.setProperty(Environment.PASS, "");

    SchemaExport export = new SchemaExport(config);
    export.create(false, true);

    sessions = config.buildSessionFactory();
}

@Override
protected void tearDown() throws Exception {
    sessions.close();
    sessions = null;

    super.tearDown();
}

いくつかのメモ:

  1. 少なくとも私が使用している HSQLDB では、スキーマの作成はそれほど高速ではありません。しかし、実際に Hibernate をテストしているテストと、モッキング可能な永続レイヤーを使用するテストを明確に分離する限り、実際のデータベースで動作する必要のあるテストはそれほど多くないため、問題ありません。
  2. 私のテストはシングルスレッドで実行されます。マルチスレッドで実行している場合は、同時に実行するテストに対して一意になるようにデータベース名を設定する必要があります。

編集:

エラー スタック トレースとコード、およびテストが独立して実行されるという事実に基づいて、PessimisticLockExceptions は、同じデータベースに対して別のスレッドで実行されているテストによって引き起こされていると思われます。おそらく、RATES テーブルでデータベース中心のデッドロックが発生することさえあります。これに対処するための 2 つのオプションは、テストで独立したデータベースを生成するか、TestNG にテストを連続して実行するように指示することです。

まず、Hibernate 接続文字列をプログラムで編集する必要があります。

jdbc:h2:mem:db1;DB_CLOSE_DELAY=0;MVCC=true

jdbc:h2:mem:<TESTNAME>;DB_CLOSE_DELAY=0;MVCC=true

または類似。JUnit では、TestCase.getName()を使用します。TestNG にも同様の機能があると思います。

別の方法は、シリアルに実行することです。TestNG のドキュメントによると、クラス (メソッドではなく) に @Test(singleThreaded=true) を追加するように注釈を付けることで、テストのシリアル化が可能になるはずです。

@Test(singleThreaded=true)
public class RatesPersistanceTest

それが少なくとも動作するはずの方法です。おそらく、試したときにメソッドに注釈を付けましたか?

于 2013-01-23T19:09:03.453 に答える
0

を使用して、次の簡単な例のようなことをしましAbstractTestNGSpringContextTestsた。

@ContextConfiguration(locations = { "file:src/test/resources/test-context.xml" })
@TransactionConfiguration(defaultRollback = true)
public class TheDAOTest extends AbstractTestNGSpringContextTests {

    private static final Logger LOG = Logger.getLogger(TheDAOTest.class);

    // N.B. this will be wired AFTER @BeforeTest !!
    @Autowired TheDAO subject;

    @BeforeMethod
    public void beforeMethod() {
        final HibernateTemplate ht = subject.getHibernateTemplate();
        ht.deleteAll(subject.listCustomers());
    }

    @Test
    public void noCustomers() {
        final List<CustomerDTO> customers = subject.listCustomers();
        assert customers != null : "listCustomers null result";
        LOG.info("listCustomers: " + customers);
        assert customers.size() == 0 : "Expected zero customers";

        final CustomerDTO customer = subject.findCustomerById(0L);
        assert customer == null : "Unexpected customer found";
    }

    @Test
    public void saveAndFind() {
        final CustomerDTO dto1 = makeDTO("0");
        final CustomerDTO saved = subject.save(dto1);
        assert saved != null ;

        final Long id1 = saved.getId();
        CustomerDTO customer = subject.findCustomerById(id1);
        assert customer != null ;
        assert customer.getId().equals(id1);
        assert customer.getTN().equals(dto1.getTN());

        List<CustomerDTO> customers = subject.listCustomers();
        assert customers != null : "listCustomers null result";
        LOG.info("saveAndFind - listCustomers: " + customers);
        assert customers.size() == 1 : "Expected one customer";

        subject.save(makeDTO("1"));

        customer = subject.findCustomerById(id1);
        assert customer != null ;
        assert customer.getId().equals(id1);

        customers = subject.listCustomers();
        assert customers != null : "listCustomers null result";
        LOG.info("saveAndFind - listCustomers: " + customers);
        assert customers.size() == 2 : "Expected two customers";
   }

   private CustomerDTO makeDTO(final String x) {
       final CustomerDTO dto = new CustomerDTO();
       dto.setX(x);
       return dto;
   }

}

完全を期すために、DTO は次のようなものです。

@Entity @Table(name = "customers") public class CustomerDTO {

    @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id = Long.MIN_VALUE;
    @Column private String X;

    public Long getId() {
        return id;
    }
    public void setId(final Long id) {
        this.id = id;
    }
    public String getX() {
        return this.X;
    }
    public void setX(final String x) {
        this.X = x;
    }
    //  etc. 
}

そして最後に、DAO:

@Component
public class TheDAO {

    public List<CustomerDTO> listCustomers() {
        return getHibernateTemplate().loadAll(CustomerDTO.class);
    }

    public CustomerDTO getCustomer(final Long id) {
        return getHibernateTemplate().get(CustomerDTO.class, id);
    }

    public List<CustomerDTO> listCustomers() {
        return getHibernateTemplate().loadAll(CustomerDTO.class);
    }

    public <T> T save(final T valueObject) {
        getHibernateTemplate().saveOrUpdate(valueObject);
        return valueObject;
    }

    public void setHibernateTemplate(final HibernateTemplate hibernateTemplate) {
        TheDAO.hibernateTemplate = hibernateTemplate;
    }

    CustomerDTO findCustomerById(final Long id) {
        return getHibernateTemplate().get(CustomerDTO.class, id);
    }

    HibernateTemplate getHibernateTemplate() {
        return TheDAO.hibernateTemplate;
    }
}
于 2012-12-27T23:08:44.403 に答える