383

私は、バックエンドでさまざまな複雑さのデータベースによって駆動される多くの Web アプリケーションを扱っています。通常、ビジネスおよびプレゼンテーション ロジックとは別のORMレイヤーがあります。これにより、ビジネス ロジックの単体テストがかなり簡単になります。個別のモジュールに実装でき、テストに必要なデータはオブジェクトのモックによって偽造できます。

しかし、ORM とデータベース自体のテストには、常に問題と妥協が伴います。

何年にもわたって、私はいくつかの戦略を試してきましたが、完全に満足できるものはありませんでした.

  • テスト データベースに既知のデータを読み込みます。ORM に対してテストを実行し、正しいデータが返されることを確認します。ここでの欠点は、テスト DB がアプリケーション データベースのスキーマ変更に対応する必要があり、同期が取れなくなる可能性があることです。また、人工データに依存しており、愚かなユーザー入力によって発生するバグを明らかにしない場合があります。最後に、テスト データベースが小さい場合、インデックスの欠落などの非効率性は明らかになりません。(OK、最後のテストは実際には単体テストを使用する目的ではありませんが、害はありません。)

  • 本番データベースのコピーをロードし、それに対してテストします。ここでの問題は、いつでも実動 DB に何が入っているか分からない可能性があることです。時間の経過とともにデータが変更された場合、テストを書き直す必要がある場合があります。

これらの戦略はどちらも特定のデータに依存しており、単体テストでは機能のみをテストする必要があると指摘する人もいます。そのために、私は提案されたのを見てきました:

  • モック データベース サーバーを使用し、特定のメソッド呼び出しに対して ORM が正しいクエリを送信していることのみを確認します。

データベース駆動型アプリケーションをテストするためにどのような戦略を使用しましたか? 何があなたにとって最も効果的でしたか?

4

7 に答える 7

163

私は実際にあなたの最初のアプローチを使用してかなりの成功を収めましたが、あなたの問題のいくつかを解決すると思う少し異なる方法で:

  1. チェックアウト後に誰でも現在のデータベース スキーマを作成できるように、スキーマ全体とそれを作成するためのスクリプトをソース管理に保持します。さらに、ビルド プロセスの一部で読み込まれるデータ ファイルにサンプル データを保持します。エラーの原因となるデータを発見したら、それをサンプル データに追加して、エラーが再発しないことを確認します。

  2. 継続的インテグレーション サーバーを使用して、データベース スキーマを構築し、サンプル データを読み込み、テストを実行します。これが、テスト データベースの同期を維持する方法です (テストを実行するたびに再構築します)。これには、CI サーバーが独自の専用データベース インスタンスへのアクセスと所有権を持っている必要がありますが、db スキーマを 1 日 3 回構築することで、配信の直前まで発見されなかったであろうエラーを劇的に発見できるようになりました (後ではない場合)。 )。すべてのコミットの前にスキーマを再構築するとは言えません。誰か?このアプローチでは、その必要はありません (そうすべきかもしれませんが、誰かが忘れても大したことではありません)。

  3. 私のグループでは、ユーザー入力は (db ではなく) アプリケーション レベルで行われるため、これは標準の単体テストによってテストされます。

Loading Production Database Copy:
これは、私の最後の仕事で使用されたアプローチでした。それはいくつかの問題の大きな苦痛の原因でした:

  1. コピーは製品版から古くなる
  2. 変更はコピーのスキーマに加えられ、運用システムには反映されません。この時点で、スキーマが分岐しています。楽しくない。

Mocking Database Server:
現在の仕事でもこれを行っています。すべてのコミットの後、モック db アクセサーが挿入されたアプリケーション コードに対して単体テストを実行します。次に、上記の完全な db ビルドを 1 日 3 回実行します。私は間違いなく両方のアプローチをお勧めします。

于 2008-09-28T03:45:30.500 に答える
60

次の理由から、常にインメモリ DB (HSQLDB または Derby) に対してテストを実行しています。

  • テスト DB に保持するデータとその理由を考えさせられます。実稼働 DB をテスト システムに持ち込むだけでは、「自分が何をしているのか、その理由がわからない。何かが壊れたら、それは私ではない!!」ということになります。;)
  • これにより、データベースを新しい場所に簡単に再作成できるようになります (たとえば、本番環境からバグを複製する必要がある場合)。
  • これは、DDL ファイルの品質に非常に役立ちます。

テストが開始されるとインメモリ DB に新しいデータがロードされ、ほとんどのテストの後、安定性を保つために ROLLBACK を呼び出します。テスト DB のデータは常に安定した状態に保ってくださいデータが常に変化する場合は、テストできません。

データは、SQL、テンプレート DB、またはダンプ/バックアップからロードされます。ダンプは、VCS に入れることができるので、読み取り可能な形式であれば好みます。それがうまくいかない場合は、CSV ファイルまたは XML を使用します。膨大な量のデータをロードする必要がある場合は、ロードしません。膨大な量のデータをロードする必要はありません:)単体テスト用ではありません。パフォーマンス テストは別の問題であり、異なるルールが適用されます。

于 2008-11-24T15:34:50.900 に答える
14

私は長い間この質問をしてきましたが、それに対する特効薬はないと思います。

私が現在行っていることは、DAO オブジェクトをモックし、データベース上に存在する可能性のあるデータの興味深いケースを表すオブジェクトの適切なコレクションのメモリ内表現を維持することです。

このアプローチで私が目にする主な問題は、DAO レイヤーとやり取りするコードのみをカバーしていて、DAO 自体をテストしていないことです。私の経験では、そのレイヤーでも多くのエラーが発生していることがわかります。また、データベースに対して実行する単体テストをいくつか保持しています (TDD を使用するため、またはローカルでのクイック テストのため)。 CI サーバーで実行されるテストは自己完結型であるべきだと考えています。

私が非常に興味深いと思うもう 1 つのアプローチは、少し時間がかかるため、常に価値があるとは限りません。それは、単体テスト内で実行される組み込みデータベースで、本番用に使用するものと同じスキーマを作成することです。

このアプローチが適用範囲を改善することに疑いの余地はありませんが、現在の DBMS と組み込みの置き換えの両方で動作させるには、できるだけ ANSI SQL に近づける必要があるため、いくつかの欠点があります。

コードにとってより関連性が高いと思われるものに関係なく、 DbUnitのように、それをより簡単にする可能性のあるプロジェクトがいくつかあります。

于 2008-09-28T03:55:29.553 に答える
13

何らかの方法でデータベースをモックできるツールがある場合でも (たとえば、この回答で確認できるjOOQの- 免責事項、私は jOOQ のベンダーで働いています) 、複雑な大規模なデータベースをモックしないことをお勧めします。クエリ。MockConnection

ORM の統合テストだけを行う場合でも、ORM がデータベースに対して非常に複雑な一連のクエリを発行することに注意してください。

  • 構文
  • 複雑
  • 注文 (!)

送信された SQL ステートメントを解釈するモック内に小さなデータベースを実際に構築しない限り、これらすべてをモックして適切なダミー データを生成するのは非常に困難です。そうは言っても、統合テストを実行できる既知のデータで簡単にリセットできる、よく知られた統合テストデータベースを使用してください。

于 2013-08-01T21:13:01.613 に答える
5

最初のものを使用します (テスト データベースに対してコードを実行します)。このアプローチであなたが提起した唯一の実質的な問題は、スキーマが同期しなくなる可能性があることです。これは、データベースにバージョン番号を保持し、バージョンの増分ごとに変更を適用するスクリプトを介してすべてのスキーマ変更を行うことで対処しています。

また、最初にテスト環境に対してすべての変更 (データベース スキーマへの変更を含む) を行うため、最終的には逆になります。すべてのテストに合格したら、スキーマの更新を本番ホストに適用します。また、実際の製品ボックスに触れる前に、データベースのアップグレードが適切に機能することを確認できるように、開発システムにテスト データベースとアプリケーション データベースの別のペアを保持しています。

于 2008-11-24T17:06:47.733 に答える
3

JDBC ベースのプロジェクト (JPA、EJB など、直接的または間接的に) の場合、データベース全体をモックアップすることはできませんが (そのような場合は、実際の RDBMS でテスト データベースを使用する方がよいでしょう)、JDBC レベルでのみモックアップできます。 .

利点は、JDBCデータ(結果セット、更新カウント、警告など)がバックエンドであっても同じであるため、その方法に伴う抽象化です:prod db、テストdb、または各テストに提供されるモックアップデータのみ場合。

ケースごとに JDBC 接続がモックアップされているため、テスト データベースを管理する必要はありません (クリーンアップ、一度に 1 つのテストのみ、フィクスチャのリロードなど)。すべてのモックアップ接続は分離されており、クリーンアップする必要はありません。各テスト ケースでは、JDBC 交換をモックアップするために必要な最小限のフィクスチャのみが提供されます。これにより、テスト データベース全体を管理する複雑さを回避できます。

Acolyte は、この種のモックアップ用の JDBC ドライバーとユーティリティを含む私のフレームワークです: http://acolyte.eu.org

于 2014-07-12T12:18:13.340 に答える
3

私は最初のアプローチを使用していますが、あなたが言及した問題に対処できるようにするために少し異なります。

DAO のテストを実行するために必要なものはすべてソース管理にあります。これには、DB を作成するためのスキーマとスクリプトが含まれています (これには docker が非常に適しています)。組み込み DB を使用できる場合は、高速化のために使用します。

説明されている他のアプローチとの重要な違いは、テストに必要なデータが SQL スクリプトまたは XML ファイルから読み込まれないことです。すべて (実質的に定数である一部の辞書データを除く) は、ユーティリティ関数/クラスを使用してアプリケーションによって作成されます。

主な目的は、テストで使用されるデータを作成することです

  1. テストに非常に近い
  2. 明示的 (データに SQL ファイルを使用すると、どのデータがどのテストで使用されているかを確認するのが非常に困難になります)
  3. 無関係な変更からテストを分離します。

これは基本的に、これらのユーティリティが、テスト自体でテストに不可欠なものだけを宣言的に指定し、無関係なものを省略できることを意味します。

それが実際に何を意味するのかを理解するために、によって書かれたComments からs で動作する DAO のテストを考えてみましょう。このような DAO の CRUD 操作をテストするには、DB にデータを作成する必要があります。テストは次のようになります。PostAuthors

@Test
public void savedCommentCanBeRead() {
    // Builder is needed to declaratively specify the entity with all attributes relevant
    // for this specific test
    // Missing attributes are generated with reasonable values
    // factory's responsibility is to create entity (and all entities required by it
    //  in our example Author) in the DB
    Post post = factory.create(PostBuilder.post());

    Comment comment = CommentBuilder.comment().forPost(post).build();

    sut.save(comment);

    Comment savedComment = sut.get(comment.getId());

    // this checks fields that are directly stored
    assertThat(saveComment, fieldwiseEqualTo(comment));
    // if there are some fields that are generated during save check them separately
    assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));        
}

これには、テスト データを含む SQL スクリプトや XML ファイルよりもいくつかの利点があります。

  1. コードの保守ははるかに簡単です (たとえば、作成者など、多くのテストで参照されるエンティティに必須の列を追加すると、多くのファイル/レコードを変更する必要はなく、ビルダーやファクトリを変更するだけで済みます)。
  2. 特定のテストに必要なデータは、他のファイルではなく、テスト自体に記述されています。この近接性は、テストを理解しやすくするために非常に重要です。

ロールバックとコミット

テストが実行されたときにコミットする方が便利だと思います。まず、コミットが発生しない場合、一部の効果 (たとえばDEFERRED CONSTRAINTS) をチェックできません。次に、テストが失敗した場合、データはロールバックによって元に戻されないため、DB で調べることができます。

当然のことながら、これには、テストによって壊れたデータが生成される可能性があり、これが他のテストの失敗につながるという欠点があります。これに対処するために、テストを分離しようとします。上記の例では、すべてのテストが新しいAuthorものを作成し、他のすべてのエンティティがそれに関連して作成されるため、衝突はまれです。壊れる可能性はあるが DB レベルの制約として表現できない残りの不変条件に対処するために、すべてのテストの後に実行される可能性のある誤った条件に対して、いくつかのプログラムによるチェックを使用します (これらは CI で実行されますが、通常はパフォーマンスのためにローカルでオフにされます)理由)。

于 2018-09-08T23:26:44.537 に答える