【超初心者向け】DBUnit超入門

# はじめに
今回は、プログラム開発では大活躍！単体テストで使用できるフレームワーク、「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プロジェクトの構築は、以前筆者が記載した、[こちら](http://qiita.com/tarosa0001/items/e5667cfa857529900216)の記事を参考にしていただけると幸いです。
プロジェクト名と構成は任意に変更してください。
今回筆者は「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
```

```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を記載しておきます。

```sql:EMPテーブルの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のデバッグログが見られるので、結構便利です。

```java:TestMain.java
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」です。
テストプログラムの内容を記載します。
ここでは前処理でテストデータの積み込みを、テストメソッドで結果データの検証をしていますが、それぞれのデータの内容は後述します。

```java: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:Before.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:After.xml
<?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のように、現在日時をセットすることも可能です。
その場合は、[こちら](https://hondou.homedns.org/pukiwiki/pukiwiki.php?xUnit%20DbUnit%20%A4%C7%A1%A2%BD%E9%B4%FC%A5%C7%A1%BC%A5%BF%A4%CB%B8%BD%BA%DF%BB%FE%B9%EF%A4%F2%C0%DF%C4%EA%A4%B7%A4%BF%A4%A4)が参考になりますのでご覧下さい。

## 4.4 Excel版
ちなみに、Excelでデータを積み込むには以下のようにします。

```Java: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`の形式で入力してください。

■Excelのサンプル
![picture_excel1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189344/80475d61-f038-f9cb-da26-304e8b73afc1.jpeg)

#5. テストプログラムの実行
さて、ここまでで準備作業を終えましたので、テスト用のプログラムを動かしてみようと思います。
ただし、DBの変化を見たいので、実行前の状態は記録しておきましょう。

# 5.1. 実行前状態
実行前のテーブルは、レコードが空の状態です。
レコードが入っていても構わないのですが、いったんわかりやすくからの状態からスタートします。

■実行前テーブル状態
![picture1.JPG](https://qiita-image-store.s3.amazonaws.com/0/189344/7f2abb97-d831-9e6d-ac09-618a485c138b.jpeg)

# 5.2. テストの実行
では、テストを実行します。
構築したプロジェクトを、JUnitテストとして実行してください。
結果は次のように、エラーはなく正常に終了したことと思います。

■JUnit実行結果
![picture2.JPG](https://qiita-image-store.s3.amazonaws.com/0/189344/49c0daaf-a565-11e3-894f-f8b55b0a7601.jpeg)

これでJUnit + DBUnitによるテストが構築→実行できました。
今回は事前準備データも結果データも一つしか用意していませんが、複数のデータを用意し、テストごとに読み込むXMLを変えれば、テストケースごとに新しいデータを自動的に積み込んで、期待した結果を自動で検証することができます。

# 6. テストの分解
ここまで、JUnit + DBUnitで自動テストを行う方法を紹介してきましたが、いかがでしょうか？
必ずしも自動テストがよいかと問われれば、そういうわけではありませんが、何度もテストをやり直したり、何度もテスト結果を取り直したりするのって、結構面倒なので、テストが自動化できるのは、多くの場合では、大きなメリットとなることと思います。

ここまでは、駆け足でDBUnitを実行することだけを説明してきましたが、ここからは、実行したテストをステップごとに分解していきます。
具体的には、次のステップに分解し、それぞれのステップで、プログラムの動きと、DBの状態を見ていきます。

1. 事前準備データの積み込み
2. テスト対象プログラム中の状態
3. テスト結果の検証

では、Eclipse上でデバッグ実行しつつ、テストプログラムの動きを見ていきましょう。

## 6.1. 事前準備データの積み込み
まずは、DBUnitによる事前準備データの積み込み時の動きを確認しましょう。


#6.1.1. 実行前状態
テーブルは、いったん空の状態戻してあります。

■実行前テーブル状態
![picture1.JPG](https://qiita-image-store.s3.amazonaws.com/0/189344/1715dd80-d4cd-ed9f-6aee-e9f1e483fd2f.jpeg)

#6.1.2. 前処理終了直後
前処理終了直後には、Before.xmlから読み込んだデータがきっちり入っていることがお分かりいただけるかと思います。
テスト対象プログラムは処理中にDBをUPDATEしているので、レコードが入っていないとうまく動作しない母図なのですが、この前処理でレコードが挿入されていず状態で開始するので正常に動作するわけです。

■プログラムの実行ステップ
![picture4.jpg](https://qiita-image-store.s3.amazonaws.com/0/189344/14be01c9-00cb-26aa-9167-ba0083b2522e.jpeg)

■DBの状態
![picture3.jpg](https://qiita-image-store.s3.amazonaws.com/0/189344/abe0e878-dc8a-d9d8-97bb-40fa12e1b2b2.jpeg)

# 6.1.3. テストプログラム中の状態
テストプログラム実行中の状態を見てみましょう。
前処理で更新対象のレコードが挿入されているので、UPDATEは正常に実行を終了し、DBの状態が変わっていることが見て取れるかと思います。

■UPDATE実行前
![picture5.jpg](https://qiita-image-store.s3.amazonaws.com/0/189344/a8c5de63-27ff-c85b-9148-ce0875286827.jpeg)

■UPDATE実行後
![a4886ebe-6a13-abf8-1d56-8a5b4f694f3f.jpeg](https://qiita-image-store.s3.amazonaws.com/0/189344/bb4efe92-3bf8-b32b-6521-59cb6fe7aeec.jpeg)


■実行後のDBの状態
![picture7.jpg](https://qiita-image-store.s3.amazonaws.com/0/189344/3ae2395c-9916-eea4-cfaa-d330a25ddbe3.jpeg)

# 6.1.4. テスト結果の検証
最後にテスト結果の検証を見てみましょう。
DBの状態が期待通りになっているので、DBUnitのアサーションにより、テストを失敗することはなく、アサーションの処理を正常に実行しています。
アサーション実行後の最終結果は、先に記載した通りです。

■アサーション実行前
![picture8.jpg](https://qiita-image-store.s3.amazonaws.com/0/189344/ac1e3f04-b0e6-62a7-6591-465d26f3987d.jpeg)

■アサーション実行後
![picture9.jpg](https://qiita-image-store.s3.amazonaws.com/0/189344/dbf7e6e9-dbf2-52b3-5fb3-79cec1dc2f6b.jpeg)

# 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