なにか作る

なにかを作るブログです。

データベースのテストに使える AssertJ-DB ライブラリの使い方と注意点

最近使った AssertJ-DB がかなり強力だったので紹介してみる。

AssertJ-DB とは

joel-costigliola.github.io

  • JUnitTestNG などで使えるデータベーステスト用のライブラリ。
    • AssertJAPI を前提とした作りにはなっているが、AssertJ-DB だけで単体として利用することも可能。
  • 以下の特徴・利点がある。
    • スタートポイントとエンドポイントを設けて、その間にデータベースに対して発生した変更のすべてを検知することができる。
    • SQL を使用する必要がない。

サンプルコード

依存性

build.gradle

dependencies {
    testCompile 'junit:junit:4.12'
    testCompile 'org.assertj:assertj-db:1.2.0'
    testRuntime 'mysql:mysql-connector-java:5.1.44'
}

テストコード

Sample.java

Changes changes = new Changes(new Source("jdbc:mysql://localhost:3306/sample", "sample", "password"));

changes.setStartPointNow();

// この間にテスト対象の処理を行う
// updateCustomer();

changes.setEndPointNow();

assertThat(changes)
    .hasNumberOfChanges(1)
    .ofModificationOnTable("CUSTOMER")
        .hasNumberOfChanges(1)
    .changeOnTable("CUSTOMER")
        .isModification()
        .rowAtEndPoint()
            .value("ID").isEqualTo(1)
            .value("NAME").isEqualTo("Yamada")
            .value("ADDRESS").isEqualTo("東京都千代田区")

説明

  • 接続先データベースの情報を与えてデータベースの変更を追跡するための Changes オブジェクトを作成する。
    • Changes 以外にも、対象のテーブルだけの変更を追跡する Table オブジェクトや特定の SQL の結果を追跡する Request オブジェクトなどもあるが、一番使い出がいいのは Changes ではないかと。
  • テスト対象処理実行前に setStartPointNow メソッドを呼び、テスト対象処理実行後に setEndPointNow メソッドを呼び出す。
    • スタートポイントとエンドポイントの間において発生したすべての変更が Changes オブジェクト内に記録される。
  • org.assertj.db.api.Assertions.assertThat メソッドに Changes オブジェクトを渡し、Fluent API でDB変更の確認を行う。
    • 確認方法は様々(公式のサンプルコードはこちら
    • 上記のサンプルコードでは以下の確認を行っている。
      • データベースに対する変更箇所が1行のみである。
      • CUSTOMER テーブルに 1 行 Update が発生している。
      • CUSTOMER テーブルに発生した変更行の値が想定どおりである。

データが登録されたことの確認サンプル

assertThat(changes)
    .hasNumberOfChanges(1)
    .ofCreationOnTable("CUSTOMER")
        .hasNumberOfChanges(1)
    .changeOnTable("CUSTOMER")
        .isCreation()
        .rowAtEndPoint()
            .value("ID").isEqualTo(1)
            .value("NAME").isEqualTo("Yamada")
            .value("ADDRESS").isEqualTo("東京都千代田区")
  • 変更とあまり変わらない。

データが削除されたことの確認サンプル

assertThat(changes)
    .hasNumberOfChanges(1)
    .ofDeletionOnTable("CUSTOMER")
        .hasNumberOfChanges(1)
    .changeOnTable("CUSTOMER")
        .isDeletion()
        .rowAtStartPoint()
            .value("ID").isEqualTo(1)
  • 消された行を特定するために Endpoint のデータではなく、StartPoint の主キーの値を確認している。

注意点

  • データベースの変更は決まった順序で並べられており、changeOnTable メソッドなどで得られる変更はこの順序に従って取得される。
    • ここでは具体的に説明しないが、大変重要な概念なので覚えておく必要あり。こちらに説明あり。
    • 例えば 2 行変更が起こった場合に、以下のテストコードは、取得されるデータベースの変更と順序が違うために失敗する。
assertThat(changes)
    .hasNumberOfChanges(2)
    .ofModificationOnTable("CUSTOMER")
        .hasNumberOfChanges(2) // ここまでのテストは成功するが、
    .changeOnTable("CUSTOMER") // データベースの変更は主キー順に並べられるため ID:1 と ID:2 に変更があった場合、最初に取得されるのは ID:1 の方なので、
        .isModification()
        .rowAtEndPoint()
            .value("ID").isEqualTo(2) // このテストで失敗する
            .value("NAME").isEqualTo("Sato")
            .value("ADDRESS").isEqualTo("東京都港区")
    .changeOnTable("CUSTOMER") // 2回目の呼び出しで ID:2 の変更が取得される
        .isModification()
        .rowAtEndPoint()
            .value("ID").isEqualTo(1)
            .value("NAME").isEqualTo("Yamada")
            .value("ADDRESS").isEqualTo("東京都千代田区")
// ちなみにこの取得順をなんとか変更できないかと調べてみたが、内部設計上どうやら無理そうであった……。
  • changeOnTableWithPks メソッドで変更のソート順を無視して変更行の抽出を行うことができるが、あくまで抽出のキーとできるのは主キーのみ。
  • データベース全体の変更点を走査するため、実行に数秒から数十秒程度、それなりに時間がかかる。
  • Fluent API なのでコードのフォーマットに気を使う必要がある。