web-dev-qa-db-ja.com

データアクセスレイヤーをテストする方法

Spring for JDBCアクセスを利用する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とスキーマに関して実稼働環境と同様の統合テストを行うべきではありませんか?

17
Michael

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

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

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

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

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

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

その後、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 は、データベースの設定、テーブルの作成、およびデータのロードにおける、より簡単なプロセスについて説明したことの多くを実現できます。何らかの理由で実際のデータベースを使用する必要がある場合は、これがはるかに優れたツールです。

上記のコードは、概念実証のために私が作成したmavenプロジェクトの一部です githubのTestingWithHsqldb

15
user40980

まず、本番環境でテストを行うべきではありません。本番環境を反映したテスト環境を用意し、そこで統合テストを行う必要があります。

そうすれば、さまざまなことができます。

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

私たちのプロジェクトでは、各開発者が空のデータベースを実行しており、その構造は本番データベースと同じです。

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

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

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

1
Carra