はじめに
今回は、プログラム開発では大活躍!単体テストで使用できるフレームワーク、「DBUnit」の使い方を紹介します。
DBUnitとは、Javaで使用するテスティングフレームワークの一つなのですが、筆者が勉強した感じでは、記事が古かったり、それだけで完全に事足りるwebが少ないように見受けられたので、筆者の備忘と、この記事をご覧いただいた方への共有も兼ねて、こちらにまとめておきます。
参考としてwebに資料を記載いただいている優しい方も、たくさんいらっしゃるんですけどねorz
#1. DBUnitとは
先にも記載しましたが、DBUnitとは、Javaプログラムのテスティングフレームワークの一つです。
テスティングフレームワークと聞くと、XUnitを想像されるかと思います。
今回の対象はJavaで実装したプログラムですから、よく使用するのはJUnitですが、DBUnitもJUnitと一緒に使います。
JUnitでは、プログラム中に処理したメソッドの戻り値や、引き渡したパラメータに対してチェックを行うことができますが、DBUnitでは、DBに更新した値に対してチェックを行うことができます。
その他に、テスト開始前に、DBにテストデータを積み込んだりすることもできます。
その存在自体を知らない人も少なくないようなのですが、使いこなせれば、単体テスト(以下、UT)が格段に「楽に、正確に」なりますので、ご興味があれば是非この記事を最後までご覧ください。
#2. テストプロジェクトの構築
まずは、DBUnitのテストに使用するテストプロジェクトを構築します。
テスティングフレームワークのテストというのも、少しおかしい気もしますが、その辺は気にしないで行きます。
筆者は以前、Mavenの勉強をして、今Mavenを推しているので、今回のテストプロジェクトは、MavenとEclipseを使用して構築します。
Mavenプロジェクトの構築は、以前筆者が記載した、こちらの記事を参考にしていただけると幸いです。
プロジェクト名と構成は任意に変更してください。
今回筆者は「dbunit-test」とし、以下のライブラリを依存関係として追加ました。
プロジェクトツリーと、pom.xmlの設定内容を記載しておきます。
プロジェクトルート
├─src
│ ├─main
│ │ ├─java
│ │ │ └─test
│ │ │ └─main
│ │ │ TestMain.java
│ │ │
│ │ └─resources
│ │
│ └─test
│ ├─java
│ │ └─test
│ │ └─main
│ │ TestMainTest.java
│ │
│ └─resources
│ logback.xml
│
├─target
│ ├─classes
│ │ │ logback.xml
│ │ │
│ │ └─test
│ │ └─main
│ │ TestMain.class
│ │
│ └─test-classes
│ │ logback.xml
│ │
│ └─test
│ └─main
│ TestMainTest.class
│
├─data
│ Before.xml --- 事前準備データ
│ After.xml --- 結果検証データ
└─pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dbunit.test</groupId>
<artifactId>dbunit-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 文字コードとJavaのバージョンの設定 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<!-- ロガーの設定 -->
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<!--
今回DBにはOracleを使用するので、JDBCドライバはOracleのものを使用します。
OracleのJDBCドライバはMavenのセントラルリポジトリにはないので、ローカルリポジトリにインストールしてあるものを使用します。
DBUnitと直接の関係はありませんが、ライブラリをローカルリポジトリにインストールする方法は、
最後に参考として記載します。
-->
<groupId>com.oracle</groupId>
<artifactId>ojdbc7</artifactId>
<version>11.2.0</version>
</dependency>
<!-- テストに使用するライブラリ -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<!-- 記事執筆時点での最新 -->
<version>2.5.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
今回この記事ではMavenを使用していますが、JUnitとDBUnitさえビルドパスに追加しておけば、Mavenである必要はりません。
JUnitはEclipseのプラグインでも構いませんので、お好みに合わせてプロジェクト設定は変更してください。
#3. テスト用テーブルの構築
テストプログラムで実装するテスト用テーブルを構築します。
今回は、Oracleインストール時に自動で生成してくれるDBと伝統のSCOTTユーザを使用します。
使用するテーブルも、教科書などでおなじみ、EMPテーブルです。
Oracleインストール時にデフォルトをで構築している場合は、初めから自動で作成されていますが、テーブルのDDLを記載しておきます。
CREATE TABLE SCOTT.EMP (
ID NUMBER NOT NULL,
NAME VARCHAR2(255) NOT NULL,
AGE NUMBER NOT NULL,
SALARY NUMBER,
JOB_TYPE VARCHAR2(20),
HIREDATE TIMESTAMP(6),
DEPARTMENT_ID NUMBER
);
テーブルは構築しますが、今回はDBUnitとしてテストデータを積み込みたいので、テーブルは空のままにしておいてください。
#4. テストプログラムの実装
プロジェクトのスケルトンを構築したところで、テストプログラムを実装します。
あくまでもサンプル程度なので、1テーブルのレコードをUPDATEするだけのプログラムです。
##4.1. テスト対象プログラムの実装
テスト対象とするサンプルプログラムは、「TestMain.java」です。
テスト用プログラムでは、logbackを使用していますが、こちらはおまけなので、使用する必要はありません。
logbackを使用すれば、DBUnitのデバッグログが見られるので、結構便利です。
package test.main;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* DBUnitテストのmainを持つクラス。
* @author tarosa0001
*/
public class TestMain {
/** ロガー */
private static Logger logger = LoggerFactory.getLogger(TestMain.class);
/** 実行するSQL */
private static final String SQL
= " update "
+ " EMP "
+ " set "
+ " name = 'tiger' "
+ " where "
+ " id = 1";
/**
* @param args
*/
public static void main(String[] args) {
logger.info("処理開始");
// ---------------------------------
// DBを更新する
// ---------------------------------
try(Connection conn = DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:ORCL", "scott", "tiger");
PreparedStatement stmt = conn.prepareStatement(SQL);
) {
conn.setAutoCommit(false);
int i = stmt.executeUpdate();
// 処理件数を表示する
logger.info("処理件数:[" + i + "]");
conn.commit();
} catch(Exception e) {
logger.error("エラー", e);
}
logger.info("処理終了");
}
}
##4.2. JUnit + DBUnitテストソースの実装
ここからが本番です。
JUnitとDBUnitを使用したテストソースを実装します。
実装対象は「TestMainTest.class」です。
テストプログラムの内容を記載します。
ここでは前処理でテストデータの積み込みを、テストメソッドで結果データの検証をしていますが、それぞれのデータの内容は後述します。
package test.main;
import static org.junit.Assert.*;
import java.io.File;
import org.dbunit.Assertion;
import org.dbunit.IDatabaseTester;
import org.dbunit.JdbcDatabaseTester;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.filter.DefaultColumnFilter;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.operation.DatabaseOperation;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* [テストクラス]<br>
* JUnitとDBUnitを使用してテストを行う。<br>
* <br>
* @author tarosa0001
*/
public class TestMainTest {
/** ロガー */
private Logger logger = LoggerFactory.getLogger(TestMain.class);
/** DBUnitのテスター */
private static IDatabaseTester databaseTester;
/**
* [前処理]<br>
* DBに事前データを準備する。<br>
* <br>
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
logger.info("前処理開始");
// --------------------------------------
// 事前準備データのINSERT
// --------------------------------------
// 事前準備データのINSERTにはスキーマも合わせて指定する
databaseTester = new JdbcDatabaseTester("oracle.jdbc.driver.OracleDriver",
"jdbc:oracle:thin:@localhost:1521:ORCL", "scott", "tiger", "scott");
// --------------------------------------
// テストデータ投入
// --------------------------------------
IDataSet dataSet = new FlatXmlDataSetBuilder().build(new File("./data/Before.xml"));
databaseTester.setDataSet(dataSet);
// DELETE→INSERTで事前準備データを用意する
databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
databaseTester.onSetup();
logger.info("前処理終了");
}
/**
* [後処理]<br>
* テスト後の後処理を行う。<br>
* DBUnitの後片付けを行う。<br>
* <br>
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
databaseTester.setTearDownOperation(DatabaseOperation.NONE);
databaseTester.onTearDown();
}
/**
* [テスト]<br>
* DBUnitを使用して、DBの更新結果を検証する。<br>
*/
@Test
public void test() {
logger.info("JUnit + DBUnitによるテスト開始。");
TestMain.main(null);
try {
// ----------------------------------
// DBUnitで更新後データチェック
// ----------------------------------
IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(new File("./data/After.xml"));
ITable expectedTable = expectedDataSet.getTable("EMP");
IDataSet databaseDataSet = databaseTester.getConnection().createDataSet();
ITable actualTable = databaseDataSet.getTable("EMP");
// 時間に対するAssertionはほぼ確実に失敗するので検証対象から除外する
ITable filteredExpectedTable = DefaultColumnFilter.excludedColumnsTable(
expectedTable, new String[]{"HIREDATE"});
ITable filteredActualTable;
filteredActualTable = DefaultColumnFilter.excludedColumnsTable(
actualTable, new String[]{"HIREDATE"});
// ---------------------------------------------------------------
// 更新結果の検証はJUnitではなくDBUnitのAssertionを使用する
// ---------------------------------------------------------------
Assertion.assertEquals(filteredExpectedTable, filteredActualTable);
} catch (Exception e) {
logger.error("エラー", e);
fail("予期しないエラーでテストが失敗しました。");
}
logger.info("JUnit + DBUnitによるテスト開始。");
}
}
4.3. 準備データの積み込み
DBUnitを使用して事前準備データを積み込みます。
事前準備データも、結果検証用データも、どちらの定義もXMLを使用します。
筆者はXMLが大嫌いなので、このような類のものは基本的にはExcelとかCSVとかで作りたいところなのですが、CSVでは1ファイル1テーブルしか定義できない、Excelではシート名がテーブル名に対して長さが足りないなど、どうしようもなく不便な要素があるので、DBUnitのテストデータはXMLを使用します。
事前準備データと、結果検証用データのXMLの内容を記載します。
事前準備データは「Before.xml」、結果検証用データは「After.xml」です。
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<!--
データセット用のXMLでは数値データも""で囲む
DATEやTIMESTAMP型の日付は「-」つなぎで指定する
-->
<EMP
ID="1"
NAME="scott"
AGE="22"
SALARY="200000"
JOB_TYPE="employee"
HIREDATE="2017-01-01 12:34:56"
DEPARTMENT_ID="1"
/>
</dataset>
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<!--
日付は検証対象外だが一応記載します。
フィルターできていることを検証するために、わざと違う日付を入力しておきます。
-->
<EMP
ID="1"
NAME="tiger"
AGE="22"
SALARY="200000"
JOB_TYPE="employee"
HIREDATE="2017-12-31 24:12:36"
DEPARTMENT_ID="1"
/>
</dataset>
上記ではDATE型の日時は固定にしていますが、SYSDATEのように、現在日時をセットすることも可能です。
その場合は、こちらが参考になりますのでご覧下さい。
4.4 Excel版
ちなみに、Excelでデータを積み込むには以下のようにします。
IDataSet dataSet = new XlsDataSet(new File("./data/before.xlsx"))
方法としてはデータセットにExcelのファイルオブジェクトを渡すだけですが、xlsxを扱うにはpoiが必要です。
poiはバージョンが新しくなるとやたら重くなるのと、ダウンロードしたzip内に依存ライブラリがそろっていなかったりするので、筆者はv3.Xを使いました。
依存関係に追加したライブラリは以下です。
■追加したライブラリ(poi3.10のzipをダウンロードすれば全部入ってます)
- poi-3.10-FINAL.jar
- poi-ooxml-3.10-FINAL.jar
- poi-ooxml-schemas-3.10-FINAL.jar
- xmlbeans-2.3.0.jar
- commons-logging-1.1.jar
- dom4j-1.6.1.jar
また、Excelはシート名にテーブル名、1行目に列名の表形式でデータを入力します。
DATEやTIMESTAMPを扱う場合は、セルの書式を文字列にしてYYYY-MM-DD HH24:MI:SS
の形式で入力してください。
#5. テストプログラムの実行
さて、ここまでで準備作業を終えましたので、テスト用のプログラムを動かしてみようと思います。
ただし、DBの変化を見たいので、実行前の状態は記録しておきましょう。
#5.1. 実行前状態
実行前のテーブルは、レコードが空の状態です。
レコードが入っていても構わないのですが、いったんわかりやすくからの状態からスタートします。
#5.2. テストの実行
では、テストを実行します。
構築したプロジェクトを、JUnitテストとして実行してください。
結果は次のように、エラーはなく正常に終了したことと思います。
これでJUnit + DBUnitによるテストが構築→実行できました。
今回は事前準備データも結果データも一つしか用意していませんが、複数のデータを用意し、テストごとに読み込むXMLを変えれば、テストケースごとに新しいデータを自動的に積み込んで、期待した結果を自動で検証することができます。
#6. テストの分解
ここまで、JUnit + DBUnitで自動テストを行う方法を紹介してきましたが、いかがでしょうか?
必ずしも自動テストがよいかと問われれば、そういうわけではありませんが、何度もテストをやり直したり、何度もテスト結果を取り直したりするのって、結構面倒なので、テストが自動化できるのは、多くの場合では、大きなメリットとなることと思います。
ここまでは、駆け足でDBUnitを実行することだけを説明してきましたが、ここからは、実行したテストをステップごとに分解していきます。
具体的には、次のステップに分解し、それぞれのステップで、プログラムの動きと、DBの状態を見ていきます。
- 事前準備データの積み込み
- テスト対象プログラム中の状態
- テスト結果の検証
では、Eclipse上でデバッグ実行しつつ、テストプログラムの動きを見ていきましょう。
##6.1. 事前準備データの積み込み
まずは、DBUnitによる事前準備データの積み込み時の動きを確認しましょう。
#6.1.1. 実行前状態
テーブルは、いったん空の状態戻してあります。
#6.1.2. 前処理終了直後
前処理終了直後には、Before.xmlから読み込んだデータがきっちり入っていることがお分かりいただけるかと思います。
テスト対象プログラムは処理中にDBをUPDATEしているので、レコードが入っていないとうまく動作しない母図なのですが、この前処理でレコードが挿入されていず状態で開始するので正常に動作するわけです。
#6.1.3. テストプログラム中の状態
テストプログラム実行中の状態を見てみましょう。
前処理で更新対象のレコードが挿入されているので、UPDATEは正常に実行を終了し、DBの状態が変わっていることが見て取れるかと思います。
#6.1.4. テスト結果の検証
最後にテスト結果の検証を見てみましょう。
DBの状態が期待通りになっているので、DBUnitのアサーションにより、テストを失敗することはなく、アサーションの処理を正常に実行しています。
アサーション実行後の最終結果は、先に記載した通りです。
#7. 最後に
長くなった今回の記事も、これで最後です。
ここまで長くにわたり、DBunitについて紹介してきましたが、いかがでしょうか?
DBUnitは、単体テストを自動化するためのツールですが、うまく使えば単体テスト以外でも、テストデータの積み込みができたり、いろいろと便利なツールになります。
ただ、便利な反面、結構手間もかかるので、いつでも使えるものではありません。
今回の記事ではテストデータが一つしかなかったので、それほど手間ではありませんでしたが、準備しなければならないテーブルがたくさんあったり、一気にたくさんのレコードを処理するようなプログラムだと、検証を自動化しようにも、膨大な量のテストデータを準備しなければならないので、相当な手間です。
そのことも踏まえたうえで、うまく使っていただければ、筆者としてそれほどにうれしいことはありません。
#8. 参考
今回の記事では直接記載しなかった、参考を記載します。
記事執筆にあたり、次のWebを参考にしました。
記事を記載しくださった管理人、作者の皆様、ありがとうございます。
■Mavenのローカルリポジトリにライブラリをインストールする方法
http://sinsengumi.net/blog/2012/12/maven%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%A7%E6%8F%90%E4%BE%9B%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E3%82%B5%E3%83%BC%E3%83%89%E3%83%91%E3%83%BC%E3%83%86%E3%82%A3jar/
■DBUnitで
http://jyukutyo.hatenablog.com/entry/20060630/1151636345