Help us understand the problem. What is going on with this article?

【超初心者向け】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プロジェクトの構築は、以前筆者が記載した、こちらの記事を参考にしていただけると幸いです。
プロジェクト名と構成は任意に変更してください。
今回筆者は「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
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を記載しておきます。

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のデバッグログが見られるので、結構便利です。

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

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」です。

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>
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のように、現在日時をセットすることも可能です。
その場合は、こちらが参考になりますのでご覧下さい。

5. テストプログラムの実行

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

5.1. 実行前状態

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

■実行前テーブル状態
picture1.JPG

5.2. テストの実行

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

■JUnit実行結果
picture2.JPG

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

6. テストの分解

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

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

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

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

6.1. 事前準備データの積み込み

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

6.1.1. 実行前状態

テーブルは、いったん空の状態戻してあります。

■実行前テーブル状態
picture1.JPG

6.1.2. 前処理終了直後

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

■プログラムの実行ステップ
picture4.jpg

■DBの状態
picture3.jpg

6.1.3. テストプログラム中の状態

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

■UPDATE実行前
picture5.jpg

■UPDATE実行後
a4886ebe-6a13-abf8-1d56-8a5b4f694f3f.jpeg

■実行後のDBの状態
picture7.jpg

6.1.4. テスト結果の検証

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

■アサーション実行前
picture8.jpg

■アサーション実行後
picture9.jpg

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした