DBアクセスまわりのコードのユニットテストを書く場合、データベースに入っているデータが変わるとテストが失敗するようになるので、
ファイルからテストデータを投入したい事があると思います。

 

やりたい事

  • テストメソッドの実行前に、データベースの既存データをDELETEして、ファイルからテストデータを投入。
  • テストメソッドが終わったらロールバック。
    ただし、コミットしたい場合もあるので、ロールバックするかコミットするかは、メソッド毎に決められるようにしたい。

 

使用するライブラリ

  • Spring
  • DbUnit
    DBまわり(Daoなど)のユニットテスト支援ツール
  • Spring Test DBUnit
    DbUnitとSpringの連携ライブラリ
  • DbUnitNG
    DomaのSELECT結果のBeanListと、DbUnitのDataSetをAssert出来るようにするライブラリ
    便利なライブラリありがとうございます!

 

DbUnitをカスタマイズして使った理由

  1. commons-langを使ってありましたが、僕は最近はLombokとGuavaを使っているのでcommons-langを入れたくなかった
  2. 僕はDomaのEntityをSNAKE_UPPER_CASEモードで使ってるのですが、DbUnintNGではBeanのフィールド名をtoLowerCaseしてあるので、Assertion.assertEqualsでfailする。
    例えば、employeeIdemployeeidとなってしまう。
    employee_idと変換する必要があった。

 

ソースはこんな感じです。

  1. RunWithアノテーションでSpringJUnit4ClassRunnerを指定します。
  2. ContextConfigurationアノテーションでテスト対象クラスと、テスト対象クラスの依存クラスを指定します。
  3. TransactionConfigurationアノテーション、Transactionalアノテーション、TestExecutionListenersアノテーションはお決まりのやつです。
  4. テストメソッドに対して、DatabaseSetupアノテーションで投入するテストデータを指定します。
    テストデータのファイルはテストクラスと同じパッケージに配置します
  5. テスト対象が検索メソッドの場合は、IDataSetに自分で期待値データを読み込み、Assertion.assertEqualsでassertします。
    この場合、期待値データのファイルはパスを指定して読み込む必要があるため、resolvePhysicalDirectoryというメソッドを作ってます
  6. テスト対象が更新系メソッドのテストの場合は、ExpectedDatabaseアノテーションで更新後のDBとassertする期待値ファイルを指定します。
    期待値ファイルはテストクラスと同じパッケージに配置します
  7. 更新系メソッドのテストの場合、テストメソッド終了後にコミットしたい場合は、@Rollback(false)と書けばOKです。
    assert failでもコミットされるので注意してください。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
        AppConfig.class,
        EmployeeRepositoryImpl.class})
@TransactionConfiguration
@Transactional
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionDbUnitTestExecutionListener.class})
public class EmployeeRepositoryTest {

    @Inject
    EmployeeRepository sut;

    /**
     * 引数で受け取ったクラスのclassファイルがある物理ディレクトリパスを取得する
     *
     * @param clazz クラス
     * @return 物理ディレクトリパス
     */
    String resolvePhysicalDirectory(Class clazz) {
        return new File(clazz.getResource("").getFile()).getPath() + File.separator;
    }

    @Test
    @DatabaseSetup("EmployeeRepositoryTest_findAll_000_data.xml")
    public void findAllのテスト() throws Exception {

        // 期待値
        FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
        String expedtedFile = "EmployeeRepositoryTest_findAll_000_expected.xml";
        IDataSet expectedDataSet = builder.build(
                new InputSource(resolvePhysicalDirectory(getClass()) + expedtedFile));

        // テスト対象メソッド実行
        List<Employee> actual = sut.findAll();

        // DataSetに変換
        IDataSet actualdDataSet = new BeanListConverter(actual).convert();

        // assert
        Assertion.assertEquals(expectedDataSet, actualdDataSet);
    }

    @Test
//    @Rollback(false)  // これを書くとコミットされる (assert failでもコミットされるので注意)
    @DatabaseSetup("EmployeeRepositoryTest_findAll_000_data.xml")
    @ExpectedDatabase(value = "EmployeeRepositoryTest_delete_000_expected.xml", table = "employee",
            query = "select * from employee order by employee_id")
    public void deleteのテスト() throws Exception {

        // 削除対象を取得
        Employee employee = sut.findById(4);

        // テスト対象メソッド実行
        sut.delete(employee);
    }
}

 

ソースは ここ に置いてます。

SpringとDomaの組み合わせ、良いですよー。