Spring Boot に限った話ではないんだけど、今回 Spring Boot で書いたので、そんなタイトルに。
必要とされるシーンが多いわりに、意外と情報が少なかったので、まとめておきます。
DbSetup を使用するものと、DBUnit を使用するもの、それぞれを紹介します。
なお、今回はテストに Groovy Spock を使用します。
検証に使用したものを GitHub に公開しておいたので、こちらを確認しながら追いかけると、より理解しやすいかもしれません。
https://github.com/yo1000/com.yo1000.edu.boot.dbtest
環境
- Java 1.8.0_91
- Maven 3.3.9 (Maven wrapper)
- Spring Boot 1.4.0.RELEASE
- Groovy 2.4.7
- Spock 1.1-groovy-2.4-rc-1
- DbSetup 2.1.0
- DBUnit 2.5.3
$ ./mvnw -version
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-11T01:41:47+09:00)
Maven home: /Users/yo1000/.m2/wrapper/dists/apache-maven-3.3.9-bin/2609u9g41na2l7ogackmif6fj2/apache-maven-3.3.9
Java version: 1.8.0_91, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.11.5", arch: "x86_64", family: "mac"
Groovy Spock の準備
テストを書く前に Spock、各種 DB テスティングフレームワークを使用して、テストを書けるように、諸々の準備を済ませておきます。
依存関係の追加
pom.xml にいろいろ書いていきます。
まずは、依存モジュールの定義。
<!-- Groovy Spock -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.1-groovy-2.4-rc-1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.1-groovy-2.4-rc-1</version>
<scope>test</scope>
</dependency>
<!-- DbSetup -->
<dependency>
<groupId>com.ninja-squad</groupId>
<artifactId>DbSetup</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
<!-- DBUnit -->
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.5.3</version>
<scope>test</scope>
</dependency>
続いて、ビルドプラグイン。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<goals>
<goal>addTestSources</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<fileset>
<directory>${pom.basedir}/src/test/groovy</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</fileset>
</sources>
</configuration>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.1.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Spec.java</include>
<include>**/*Tests.java</include>
<include>**/*Specs.java</include>
</includes>
</configuration>
</plugin>
テストデータベースの用意
今回のデモプロジェクトでは、H2 データベースをメモリモードで使用していますが、基本的にはどのようなデータベースでも構いません。
以下のようなテーブルを用意しました。
Column | Type |
---|---|
GROUP_ID | VARCHAR(80) |
ARTIFACT_ID | VARCHAR(80) |
DESC | VARCHAR(200) |
CREATE TABLE TEST_FRAMEWORK (
GROUP_ID VARCHAR(80),
ARTIFACT_ID VARCHAR(80),
DESC VARCHAR(200)
);
DbSetup によるテスト
DbSetup によるテストは、テストデータもコード内に収まるため、非常に理解しやすく、簡単です。
とくに内容にひねりもないので、そのままコードを載せておきます。
package com.yo1000.edu.boot.dbtest
import com.ninja_squad.dbsetup.DbSetup
import com.ninja_squad.dbsetup.Operations
import com.ninja_squad.dbsetup.destination.DataSourceDestination
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification
import spock.lang.Unroll
import javax.sql.DataSource
@SpringBootTest
@Unroll
class TestFrameworkRepositoryDbSetupSpec extends Specification {
@Autowired
Application.TestFrameworkRepository frameworkRepository
@Autowired
DataSource dataSource
def setup() {
def destination = new DataSourceDestination(dataSource)
def insertItems = Operations.insertInto('TEST_FRAMEWORK')
.columns('GROUP_ID' , 'ARTIFACT_ID' , 'DESC')
.values('com.ninja-squad' , 'DbSetup' , '小さいデータ向き。コードとデータを一緒に管理したい場合にオススメ。')
.values('org.dbunit' , 'dbunit' , '大きいデータ向き。大量データの集計などをテストしたい場合にオススメ。')
.values('Null Example' , 'Null Example', null)
.build()
new DbSetup(destination,
Operations.sequenceOf(
Operations.truncate('TEST_FRAMEWORK'),
insertItems
)
).launch()
}
def "dbsetup demo"() {
expect:
def items = frameworkRepository.findAll()
items.each {
println 'groupId: ' + it.get('groupId')
println 'artifactId: ' + it.get('artifactId')
println 'desc: ' + it.get('desc')
println 'desc is null?: ' + (it.get('desc') == null)
}
assert items.size() > 0
}
}
DBUnit によるテスト
DBUnit によるテストでは、テストデータは外部ファイルに分かれます。
そのため、DbSetup に比べると、レビューコストは少々膨らんでしまいます。
一方、テストデータファイルの形式は、今回使用する DBUnit 2.5.3 では、XML、CSV、Excel がサポートされているため、集計処理のテストなどで、大量のテストデータが必要になる場合には、Excel などのスプレッドシートエディタでデータの量産がしやすいといった利点もあります。
わたしのオススメは、GitHub などでレビューを行いやすいプレーンテキストであり、インデントを意識すれば表形式に近い表現のできる CSV です。
そのため、今回は CSV を使用したサンプルを紹介します。
必要なファイルセット
DBUnit でのテストに必要なファイルは、テストコードと、テストデータだけではありません。
以下のファイルセットが必要になります。
- テストコード
- テストデータ
table-ordering.txt
なお、テストデータファイルと、table-ordering.txt
は、同一のディレクトリに配置されている必要があります。
テストデータ
テストデータファイルは、ファイル名を テーブル名.拡張子
という形で用意します。
また、先の説明で触れているとおり、XML、CSV、Excel いずれかの形式でデータを表現できます。
今回は、TEST_FRAMEWORK
というテーブルを使用し、CSV 形式のテストデータファイルを用意するので、TEST_FRAMEWORK.csv
となります。
また、テストデータの表現については、以下のような制約があります。
カラム表現に関する制約
- カラム名は、1行目に列挙する
- カラム名は、ダブルクオート (
"
) で囲わないと、スペースも含めてカラム名として扱われる
データ表現に関する制約
- 文字データは、ダブルクオートで囲って表現する
- 日付データは、ダブルクオートで囲って表現する
- 数値データは、ダブルクオートをつけず表現する
- NULL 値は、ダブルクオートをつけず、小文字 (
null
) で表現する
以上を踏まえて、以下のようなファイルを用意します。
"GROUP_ID" , "ARTIFACT_ID" , "DESC"
"com.ninja-squad" , "DbSetup" , "小さいデータ向き。コードとデータを一緒に管理したい場合にオススメ。"
"org.dbunit" , "dbunit" , "大きいデータ向き。大量データの集計などをテストしたい場合にオススメ。"
"Null Example" , "Null Example", null
table-ordering.txt
このファイルは、外部キー制約などに起因する、テーブル間の処理順序を定義するために使用します。
テストデータの流し込みを行いたい順番に、改行で区切って、テーブル名を列挙します。
TEST_FRAMEWORK
テストコード
ここまでできてしまえば、あとは DbSetup と大きな違いはありません。
setup
でテストデータを用意して、expect
などでテストを行うだけです。
package com.yo1000.edu.boot.dbtest
import com.yo1000.edu.boot.dbtest.Application.TestFrameworkRepository
import org.dbunit.DataSourceDatabaseTester
import org.dbunit.operation.DatabaseOperation
import org.dbunit.util.fileloader.CsvDataFileLoader
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.util.ResourceUtils
import spock.lang.Specification
import spock.lang.Unroll
import javax.sql.DataSource
@SpringBootTest
@Unroll
class TestFrameworkRepositoryDbUnitSpec extends Specification {
@Autowired
TestFrameworkRepository frameworkRepository
@Autowired
DataSource dataSource
def setup() {
def databaseTester = new DataSourceDatabaseTester(dataSource)
databaseTester.setUpOperation = DatabaseOperation.CLEAN_INSERT
def loader = new CsvDataFileLoader()
databaseTester.dataSet = loader.loadDataSet(ResourceUtils.getURL("classpath:dbunit/")) // Requires tail slash
databaseTester.onSetup()
}
def "dbunit demo"() {
expect:
def items = frameworkRepository.findAll()
items.each {
println 'groupId: ' + it.get('groupId')
println 'artifactId: ' + it.get('artifactId')
println 'desc: ' + it.get('desc')
println 'desc is null?: ' + (it.get('desc') == null)
}
assert items.size() > 0
}
}
まとめ
記述の違いこそあれ、いずれの方法であっても、同様のテストが可能なことは理解できたかと思います。
最後に個人的な所感に基づいた、それぞれのメリットと、使い分けのポイントなどを紹介しておわります。
DbSetup | DBUnit | |
---|---|---|
テストの保守性 | ||
レビューの容易さ | ||
大量データ表現 |
DbSetup の使いどころ
- テストデータのパターンは必要だが、物量は必要ない場合に、オススメ
- テストデータも含めて、しっかりとレビューを行いたい場合にも効果的
DBUnit の使いどころ
- 集計処理のテストなど、テストデータの物量が必要になってくる場合に、オススメ
補足
2016-12-11 追記:
コメントでもご指摘を頂いておりますが、テストでトランザクションを使用して、挿入したテストデータをロールバックしようとした場合、DbSetup では、これが一部で上手く動作しません。少し手を加えれば、DbSetup でもテストデータのロールバックを扱うことができますが、提供されたデフォルトのテスト機能と方法を使用した場合、DbSetup ではこれが動作しない点には注意が必要となります。
詳しくは以下をご確認ください。
Spring Boot, Spock, DBUnit でトランザクションを使ったテスト #DbSetup でのトランザクション
Spring Boot, Spock, DbSetup でトランザクションを使ったテスト