SpringBoot2系にDBUnitを適用する

 

アソビュー! Advent Calendar 2020 の15日目です。

アソビュー!のバックエンドエンジニアをしている茶色いネズミです。今回はデータベースの試験をコード化できるDBUnitをSpringBoot2系で動作するまでの手順をかんたんに紹介します。

DBUnitはRDBを使ったデータのテストを、JUnitを使ってテストケースを記載します。既存のデータベースをテストの初期状態とするか、初期投入するデータをあらかじめ定義しておいた状態でデータベースのアクセスを実行し、その結果を検証できます。検証結果はJUnitのコードで個別に検証しても良いですし、複数行・複数テーブルを丸ごと一致するかの検証も可能です。

テスト用のデータについては、既存のデータをそのまま用いるか、または初期投入用の定義を作るかを選びます。本稿では作成済みのデータベースならびにテーブルを用意し、SpringBootからデータベースを接続したテストの試験を行います。

検証環境

本稿では以下の環境ならびにバージョンで動作確認をしています。

  • Open JDK 11
  • SpringBoot 2.4.0
  • Mybatis ( mybatis-spring-boot-starter 2.1.4 | mybatis-spring-boot-starter-test 2.1.4 )
  • JUnit5
  • DBUnit 2.7.0
  • spring-test-dbunit 1.3.0
  • IntelliJ IDEA 2020–3 | Eclipse 2020–12(Pleiades all in one Eclipse)

検証用コードは https://github.com/A-pZ/spring-dbunit-sample に公開しています。

テスト資源とテスト内容

今回は簡易なケースとして、単一テーブルの検索結果が想定どおりかを検証します。

データベースのアクセスは半O/Rマッピングフレームワークである MyBatisを採用しました。DBUnitそのものはデータベースのアクセス方法は特に問いません。

テスト対象テーブル

テーブル名:item 、カラムは簡単に2つ、番号を示す id、名前が入るnameです。

検証テーブル:item

テスト内容

  • itemに存在するレコードを検索
  • 既存テーブルを使って、初期投入データを用意する

DBUnitはテスト実行前に投入データを読み込み、テスト終了後に投入前の状態に戻す機能もありますので、テストを行いたい既存のテーブルをそのまま使えます。

投入するデータの定義は、XML、エクセル、CSVなどが選べます。今回は扱いが簡易なCSVを使います。データを定義するファイルはJavaのクラスパスが適用された箇所に配置します。今回は、src/main/resources 直下に配置します。

データ投入にCSVを利用する場合は、以下のルールがあります。

  • ファイル名は「テーブル名.csv」
  • データの投入手順を記述した「table-ordering.txt」を投入データCSVと同ディレクトリに配置する

item.csv

id,name
1,aaa
2,bbb
3,ccc
6,ddd

table-orderting.txt

item

次にテスト対象のデータアクセスを行うJavaコードです。MyBatisを利用していますので、以下のMapper定義を用意します。

ItemMapper

テストクラス

検証を行うテストクラスは以下のようになります。

ItemMapperDBUnitTest

@MybatisTest : SpringBoot+Mybatis+JUnit用のアノテーションで、Springのコンテキスト設定を利用することを宣言

@AutoConfigureTestDatabase:MyBatisがテストで利用するデータソースの設定。replace属性:NONEを指定することで、SpringBoot内で設定しているデータソースのデータベース定義をそのまま利用する

@TestExecutionListeners:DBUnit実行時の設定を記述。dataSetLoader属性で初期投入するデータの定義を読み込むクラスを指定、databaseConnection属性でDBUnitが利用しているデータベース接続コンテキスト名を記述

そして、テストメソッドまたはテスト初回起動時に記載する@DatabaseSetupにて、先程指定したDBUnitが利用するデータベース接続設定である databaseConnection を定義します。

connection属性:DBUnitが利用するデータベース接続名を宣言(後述)
value属性:投入データが存在するディレクトリ(クラスパスからの相対パス)
type属性:データの投入方法。CLEAN_INSERTでテーブルの中を一度消去した後に投入データを入れる。

DBUnitが利用するデータベース接続

databaseConnection属性で指定している内容は、別途Springのコンテキストに登録しています。

TestDatabaseConfig

 

テストデータの読み込み
@DbUnitConfiguration(dataSetLoader = CSVDataSetLoader.class, ...)

にて宣言したCSVからデータを読み込むDBUnit用のクラスは以下のようになります。Spring-DBUnit用のAbstraceDataSetLoaderを継承して、読み込んだ結果を返します。データフォーマットごとにDBUnitのレコード型であるDataSetクラスがありますので、基本はこれにラップするのみで完結します。

 

テストを実行する

準備ができましたので単体テストクラスを実行します。

テスト投入用CSVでは、以下のように

id,name
1,aaa
2,bbb
3,ccc
6,ddd

としてあり、テストコードでは

  public void 検索() {
var items = mapper.findById("6");
assertTrue(!items.isEmpty());
assertTrue(items.size() == 1);
assertEquals("ddd", items.get(0).getName());
}

と、id が 6のレコードを検索した結果、以下を期待しています。

  • 検索結果が1レコードであること
  • 取得したレコードのname値が ddd であること

実行結果は次のように、すべて成功したので緑になりました。

テスト結果(正常)

先ほどのassertEqualsの期待値を eee とすると

テスト結果(エラー検出)
org.junit.ComparisonFailure: expected:<[eee]> but was:<[ddd]>
at org.junit.Assert.assertEquals(Assert.java:117)
at org.junit.Assert.assertEquals(Assert.java:146)
at com.github.apz.sample.mapper.ItemMapperDBUnitTest.検索(ItemMapperDBUnitTest.java:35)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

このように投入データと期待結果の違いをテストできました。

テスト終了後のデータベースを見ると以下のように初期投入しているデータは入っておらず、テスト前の状態に戻っていることが確認できます。

今後の展開

テスト対象のテーブルを広げ、テスト内容を増やしていくことで、より複雑なクエリやデータの状態でテストを流してロジックやデータの異常を検知することや、初期投入データを本番相当のデータベースで試し、複雑なクエリの動作検証を試すための手順や確認が簡単になります。

またテストコードにspock framweork (http://spockframework.org/)などのテスティングフレームワークを使ってテストコードとその仕様をより簡単に定義できると、データベースを使うロジックの試験だけを切り取って検証しやすくなるでしょう。クエリの検証を行いたい場合や、動的に切り替わるクエリがあった場合の動作試験にも使えますし、初期投入データを既存データベース+データもそのまま採用する方法でも行えますから、「データベースがこの状態のときには、クエリの結果はこうなるはずである」が手軽に作れますので、何かとメリットは大きいでしょう。

最後に

アソビュー!ではバックエンドを支えるJavaエンジニアも積極的に募集しています。「ワクワクをすべての人に」届けるサービスを一緒に盛り上げていきませんか?少しでも興味がありましたらお気軽に募集していただけますと嬉しく思います。

https://www.wantedly.com/projects/230159