データアクセスレイヤーのテスト方法


17

JDBCアクセスにSpringを使用するDAOメソッドがあります。これは、アイテムを販売する売り手の成功率を計算します。

コードは次のとおりです。

public BigDecimal getSellingSuccessRate(long seller_id) {
    String sql = "SELECT SUM(IF(sold_price IS NOT NULL, 1, 0))/SUM(1) 
                  FROM transaction WHERE seller_id = ?";
    Object[] args = {seller_id};
    return getJdbcTemplate().queryForObject(sql, args, BigDecimal.class);
}

このメソッドまたはDAOメソッドをJUnitでテストするにはどうすればよいですか?データアクセスロジックをテストするためのベストプラクティスは何ですか?いくつかのデータがロードされた埋め込み可能なデータベースに対してテストすることを考えていますが、RDBMSとスキーマの点で本番環境と同様の統合テストを行うべきではありませんか?


DBUnitをチェックしてください。それはあなたの問題を解決するために特別に作られました。
セルジオ

回答:


15

ユニットテストに「実際の」データベースを使用する場合の問題は、テストのセットアップ、停止、および分離です。完全に新しいMySQLデータベースを起動し、1つの単体テストのためだけにテーブルとデータを作成する必要はありません。この問題は、データベースの外部の性質に関係しており、テストデータベースがダウンしているため、ユニットテストは失敗します。また、テスト用の一意のデータベースがあることを確認することにも問題があります。それらは克服できますが、より簡単な答えがあります。

データベースのモックは、1つの選択肢である、それが実行されている実際のクエリをテストしません。DAOからのデータがシステムを適切に通過することを確認したい場合は、はるかに簡単なソリューションとして使用できます。しかし、DAO自体をテストするには、データとクエリが適切に実行されるDAOの背後に何かが必要です。

最初に行うことは、メモリ内データベースを使用することです。 HyperSQLは、別のデータベースの方言をエミュレートする機能を備えているため、このための優れた選択肢です。したがって、データベース間のわずかな違いは同じままです(データ型、関数など)。hsqldbには、単体テスト用の優れた機能もいくつかあります。

db.url=jdbc:hsqldb:file:src/test/resources/testData;shutdown=true;

これにより、データベースの状態(テーブル、初期データ)がtestDataファイルからロードされます。 shutdown=true最後の接続が閉じたときにデータベースを自動的にシャットダウンします。

依存性注入を使用して、ユニットテストで本番(またはテスト、またはローカル)ビルドが使用するデータベースとは異なるデータベースを選択します。

次に、DAOは、データベースに対してテストを起動できる注入されたデータベースを使用します。

単体テストは次のようになります(簡潔さのために含まれていない退屈なものの束):

    @Before
    public void setUpDB() {
        DBConnection connection = new DBConnection();
        try {
            conn = connection.getDBConnection();
            insert = conn.prepareStatement("INSERT INTO data (txt, ts, active) VALUES (?, ?, ?)");
        } catch (SQLException e) {
            e.printStackTrace();
            fail("Error instantiating database table: " + e.getMessage());
        }
    }

    @After
    public void tearDown() {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void addData(String txt, Timestamp ts, boolean active) throws Exception {
        insert.setString(1, txt);
        insert.setTimestamp(2, ts);
        insert.setBoolean(3, active);
        insert.execute();
    }

    @Test
    public void testGetData() throws Exception {
        // load data
        Calendar time = Calendar.getInstance();
        long now = time.getTimeInMillis();
        long then1h = now - (60 * 60 * 1000);  // one hour ago
        long then2m = now - (60 * 1000 * 2);   // two minutes ago
        addData("active_foo", new Timestamp(then1h), true);     // active but old
        addData("inactive_bar", new Timestamp(then1h), false);  // inactive and old
        addData("active_quz", new Timestamp(then2m), true);     // active and new
        addData("inactive_baz", new Timestamp(then2m), false);  // inactive and new

        DataAccess dao = new DataAccess();
        int count = 0;
        for (Data data : dao.getData()) {
            count++;
            assertTrue(data.getTxt().startsWith("active"));
        }

        assertEquals("got back " + count + " rows instead of 1", count, 1);
    }

したがって、DAOを呼び出す単体テストがあり、テスト中に存在するオンザフライデータベースにセットアップされたデータを使用しています。実行前、または既知の状態に復元する前に、外部リソースやデータベースの状態を心配する必要はありません(「既知の状態」は「存在しない」ため、元に戻すのは簡単です)。

DBUnitは、データベースのセットアップ、テーブルの作成、データのロードにおいて、私が説明したより単純なプロセスの多くを行うことができます。何らかの理由で実際のデータベースを使用する必要がある場合、これははるかに優れたツールです。

上記のコードは、githubのTestingWithHsqldbの概念実証のために書いたMavenプロジェクトの一部です


2
HSQLが別のdbベンダーの方言をモックできる部分については知りませんでした。ありがとうございました。
マイケル

1
これは、を介して行うことができます@Dog データベース・プロパティのようなsql.syntax_mys=true方法のHSQLDBの作品変更する:「trueに設定すると、このプロパティを、TEXTとAUTO_INCREMENT種類のサポートを有効にしても、この方言のいくつかの他の側面との互換性を可能にします。」一方sql.syntax_ora=true、「このプロパティをtrueに設定すると、非標準型のサポートが有効になります。また、DUAL、ROWNUM、NEXTVAL、CURRVAL構文が有効になり、この方言の他の側面との互換性も可能になります。」

DBUnitが道です:)
Silviu Burcea

@SilviuBurcea DBUnitは、手作業で行うよりも複雑なデータベーステスト環境をセットアップする「配管」の多くを確実に容易にします。必要に応じて手動で行う方法を知ることは、まだ便利です(上記の「手動」アプローチは、DBUnitがオプションではない他の言語に移行できます)。


2

まず、実稼働環境でテストを実行しないでください。実稼働環境をミラーリングするテスト環境を用意し、そこで統合テストを実行する必要があります。

そうすれば、多くのことができます。

  • Mockitoなどのモックフレームワークを使用して、適切なSQLがモックアイテムに送信されているかどうかを確認する単体テストを作成します。これにより、メソッドが実行するはずの処理が確実に実行され、統合が画像から除外されます。
  • 単体テストでテストしたSQLの適切性を示すテストSQLスクリプトを作成します。これは、テストスクリプトに基づいてEXPLAINなどを実行できるため、発生する可能性のあるチューニングの問題に役立ちます。
  • @Sergioで述べられているように、DBUnitを使用します。

実稼働環境を本当に言ったとき、私は本当にそれのシミュレーションを意味しました。お返事ありがとうございます。それも私が学びたいと思っていたものですので、私はモッキートを見ていきます。
マイケル

1

このプロジェクトでは、各開発者は空のデータベースを実行しています。その構造は本番データベースと同じです。

各ユニットテストTestInitializeでは、データベースへの接続とトランザクションに加えて、各テストに必要ないくつかのデフォルトオブジェクトを作成します。そして、各メソッドまたはクラスの終了後にすべてがロールバックされます。

このようにして、SQLレイヤーをテストすることができます。実際、すべてのクエリまたはデータベース呼び出しは、この方法でテストする必要があります。

欠点は、処理が遅いため、通常の単体テストとは別のプロジェクトに配置することです。メモリ内のデータベースを使用してこれを高速化することは可能ですが、考え方は同じです。


インメモリデータベースを使用する場合、トランザクションのロールバックの代わりに、すべてのテストスイートを実行する前にドロップ作成アプローチを使用できます。これは、はるかに高速です。
ダウンヒル

以前はそのようにしようとは思わなかった。私たちのテストでは、ほとんどのテストで一意のユーザー「x」が作成されます。dbを一度作成すると、テストを変更してそれらのオブジェクトを再利用することになります。
カーラ

私たちは同じページにいて、あなたのアプローチが好きです。このアプローチにより、順序に関係なく各テストケースを独立して実行でき、実行前にデータテーブルの状態が同じになることが保証されます。
ダウンヒル

それは正しいです、その順序は重要ではありません。ビルドPCとローカルマシンでユニットテストの実行順序が異なるため、以前にテストが失敗するのを見てきました。
カーラ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.