2

私は現在新しいプロジェクトを開始しており、約190のリポジトリテストがあります。私が気づいたことの1つは、なぜこれが発生するのか完全にはわかりませんが、HSQLDB(2.2.8)に対する統合テストの実行速度が思ったよりもはるかに遅いことです。

各テストの前にデータを挿入する際のボトルネックを追跡したと思います。ほとんどのテストでは、データベースをセットアップするためだけに.15〜.38秒の範囲です。これは受け入れがたい。インメモリデータベースの方がはるかに高速であると想像していました:(

これが私のすべてのリポジトリテストが拡張するデータベーステストクラスです:

@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
@TransactionConfiguration(defaultRollback=true)
@Transactional
public abstract class DatabaseTest {

    public static final String TEST_RESOURCES = "src/test/resources/";

    @Autowired
    protected SessionFactory sessionFactory;

    @Autowired
    protected UserRepository userRepository;

    @Autowired
    protected DataSource dataSource;

    protected IDatabaseTester databaseTester;

    protected Map<String, Object> jdbcMap;
    protected JdbcTemplate jdbcTemplate;

    @PostConstruct
    public void initialize() throws SQLException, IOException, DataSetException {
        jdbcTemplate = new JdbcTemplate(dataSource);

        setupHsqlDb();

        databaseTester = new DataSourceDatabaseTester(dataSource);
        databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
        databaseTester.setTearDownOperation(DatabaseOperation.NONE);
        databaseTester.setDataSet(getDataSet());
    }

    @Before
    public void insertDbUnitData() throws Exception {
        long time = System.currentTimeMillis();

        databaseTester.onSetup();

        long elapsed = System.currentTimeMillis() - time;
        System.out.println(getClass() + " Insert DB Unit Data took: " + elapsed);
    }

    @After
    public void cleanDbUnitData() throws Exception {
        databaseTester.onTearDown();
    }

    public IDataSet getDataSet() throws IOException, DataSetException {
        Set<String> filenames = getDataSets().getFilenames();

        IDataSet[] dataSets = new IDataSet[filenames.size()];
        Iterator<String> iterator = filenames.iterator();
        for(int i = 0; iterator.hasNext(); i++) {
            dataSets[i] = new FlatXmlDataSet(
                new FlatXmlProducer(
                    new InputSource(TEST_RESOURCES + iterator.next()), false, true
                )
            );
        }

        return new CompositeDataSet(dataSets);
    }

    public void setupHsqlDb() throws SQLException {
        Connection sqlConnection = DataSourceUtils.getConnection(dataSource);
        String databaseName = sqlConnection.getMetaData().getDatabaseProductName();
        sqlConnection.close();

        if("HSQL Database Engine".equals(databaseName)) {
            jdbcTemplate.update("SET DATABASE REFERENTIAL INTEGRITY FALSE;");

            // MD5
            jdbcTemplate.update("DROP FUNCTION MD5 IF EXISTS;");
            jdbcTemplate.update(
                "CREATE FUNCTION MD5(VARCHAR(226)) " +
                    "RETURNS VARCHAR(226) " +
                    "LANGUAGE JAVA " +
                    "DETERMINISTIC " +
                    "NO SQL " +
                    "EXTERNAL NAME 'CLASSPATH:org.apache.commons.codec.digest.DigestUtils.md5Hex';"
            );
        } else {
            jdbcTemplate.update("SET foreign_key_checks = 0;");
        }
    }

    protected abstract DataSet getDataSets();

    protected void flush() {
        sessionFactory.getCurrentSession().flush();
    }

    protected void clear() {
        sessionFactory.getCurrentSession().clear();
    }

    protected void setCurrentUser(User user) {
        if(user != null) {
            Authentication authentication = new UsernamePasswordAuthenticationToken(user,
                user, user.getAuthorities());

            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    }

    protected void setNoCurrentUser() {
        SecurityContextHolder.getContext().setAuthentication(null);
    }

    protected User setCurrentUser(long userId) {
        User user = userRepository.find(userId);

        if(user.getId() != userId) {
            throw new IllegalArgumentException("There is no user with id: " + userId);
        }

        setCurrentUser(user);

        return user;
    }

    protected User getCurrentUser() {
        return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }

}

これが私のアプリケーションコンテキストに関連するBeanです。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:applicationContext.properties"/>
</bean>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
      destroy-method="close">
    <property name="driverClass" value="${database.driver}"/>
    <property name="jdbcUrl" value="${database.url}"/>
    <property name="user" value="${database.username}"/>
    <property name="password" value="${database.password}"/>
    <property name="initialPoolSize" value="10"/>
    <property name="minPoolSize" value="10"/>
    <property name="maxPoolSize" value="50"/>
    <property name="idleConnectionTestPeriod" value="100"/>
    <property name="acquireIncrement" value="2"/>
    <property name="maxStatements" value="0"/>
    <property name="maxIdleTime" value="1800"/>
    <property name="numHelperThreads" value="3"/>
    <property name="acquireRetryAttempts" value="2"/>
    <property name="acquireRetryDelay" value="1000"/>
    <property name="checkoutTimeout" value="5000"/>
</bean>

<bean id="sessionFactory"
      class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>...</value>
        </list>
    </property>
    <property name="namingStrategy">
        <bean class="org.hibernate.cfg.ImprovedNamingStrategy"/>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="javax.persistence.validation.mode">none</prop>

            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
            <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}
            </prop>
            <prop key="hibernate.generate_statistics">false</prop>

            <prop key="hibernate.show_sql">false</prop>
            <prop key="hibernate.format_sql">true</prop>

            <prop key="hibernate.cache.use_second_level_cache">false</prop>
            <prop key="hibernate.cache.provider_class">

            </prop>
        </props>
    </property>
</bean>

<bean class="org.springframework.orm.hibernate4.HibernateExceptionTranslator"/>

<bean id="transactionManager"
      class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

より少ないデータを挿入しようとするために、各テストクラスが必要なデータのみをロードするDataSet列挙型を選択できるようにします。これは次のように指定されます。

public enum DataSet {
    NONE(create()),
    CORE(create("core.xml")),
    USERS(combine(create("users.xml"), CORE)),
    TAGS(combine(create("tags.xml"), USERS)),

これにより、実行速度が速くなるのではなく遅くなる可能性がありますか?コアxml(言語、州など)のみが必要な場合は、それらのレコードをロードするだけで済みます。これでテストスイートが速くなると思いましたが、それでも遅すぎます。

テストクラスごとに特別に設計された個別のxmlデータセットを作成することで、時間を節約できます。これにより、挿入ステートメントの一部が切り取られます。ただし、単一のxmlデータセットに20個の挿入ステートメントがある場合でも(したがって、データセットをJavaコードに直接インライン化する以外の最小のI / O損失)、各テストには、の初期化中に.1〜.15秒かかります。データベースデータ!20レコードをメモリに挿入するのに0.15秒かかるとは信じられません。

Spring3.0とHibernate3.xを使用する他のプロジェクトでは、各テストの前にすべてを挿入するのに30ミリ秒かかりますが、実際にはテストごとに100行以上を挿入しています。インサートが20個しかないテストでは、遅延がまったくないかのように飛行します。これは私が期待したことです。問題はSpringのアノテーション、またはクラスでのアノテーションの設定方法にあると思い始めていますDatabaseTest。これが基本的に今の唯一の違いです。

また、私のリポジトリは、HibernateTemplateの代わりにsessionFactory.getCurrentSession()を使用しています。Springのテストクラスは非推奨になっているため、Springのアノテーションベースの単体テストを使い始めたのはこれが初めてです。それが彼らが遅くなっている理由でしょうか?

それを理解するためにあなたが知る必要がある他の何かがあれば、私に知らせてください。私はちょっと困惑しています。

編集:私は答えを入れました。問題はhsqldb2.2.xでした。2.0.0に戻すと、問題が修正されます。

4

2 に答える 2

3

問題はHsqldb2.2.8でした。2.0.0に戻したところ、パフォーマンスが8〜10倍以上向上しました。150〜280ミリ秒かかる代わりに、7〜15ミリ秒(場合によっては20ミリ秒)になりました。

私のテストスイート全体(490テスト)は、80秒ではなく18秒で実行されるようになりました。

私は皆へのメモを推測します:hsqldb2.2.xを避けてください。マルチスレッドのサポートが追加されたため、このタイプのユースケースでパフォーマンスの問題が発生したと思います。

于 2012-10-14T00:13:56.817 に答える
3

それはかなり速く見えます、私見。私ははるかに遅い統合テストを見てきました。そうは言っても、テストを高速化できるさまざまなアプローチがあります。

  • 挿入されるデータの量を減らす
  • データセットが同一であり、前のテストが読み取り専用テストである場合は、前のテストと同じデータを挿入しないでください(これは、特に各テストの最後にロールバックする場合によくあることです)。

DbUnitでそれを行うことは可能であると思います。別のフレームワークを使用する準備ができている場合は、それをすぐにサポートする独自のDbSetupを使用できます。

于 2012-10-13T19:54:36.713 に答える