LoginSignup
30
33

More than 5 years have passed since last update.

Spring Boot で DB アクセスまわりのテスト

Last updated at Posted at 2016-09-25

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 にいろいろ書いていきます。
まずは、依存モジュールの定義。

pom.xml <dependencies>
<!-- 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>

続いて、ビルドプラグイン。

pom.xml <build><plugins>
<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 によるテストは、テストデータもコード内に収まるため、非常に理解しやすく、簡単です。
とくに内容にひねりもないので、そのままコードを載せておきます。

TestFrameworkRepositoryDbSetupSpec.groovy
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) で表現する

以上を踏まえて、以下のようなファイルを用意します。

TEST_FRAMEWORK.csv
"GROUP_ID"          , "ARTIFACT_ID" , "DESC"
"com.ninja-squad"   , "DbSetup"     , "小さいデータ向き。コードとデータを一緒に管理したい場合にオススメ。"
"org.dbunit"        , "dbunit"      , "大きいデータ向き。大量データの集計などをテストしたい場合にオススメ。"
"Null Example"      , "Null Example", null

table-ordering.txt

このファイルは、外部キー制約などに起因する、テーブル間の処理順序を定義するために使用します。
テストデータの流し込みを行いたい順番に、改行で区切って、テーブル名を列挙します。

table-ordering.txt
TEST_FRAMEWORK

テストコード

ここまでできてしまえば、あとは DbSetup と大きな違いはありません。
setup でテストデータを用意して、expect などでテストを行うだけです。

TestFrameworkRepositoryDbUnitSpec.groovy
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
テストの保守性 :thumbsup: :cold_sweat:
レビューの容易さ :thumbsup: :cold_sweat:
大量データ表現 :cold_sweat: :thumbsup:

DbSetup の使いどころ

  • テストデータのパターンは必要だが、物量は必要ない場合に、オススメ
  • テストデータも含めて、しっかりとレビューを行いたい場合にも効果的

DBUnit の使いどころ

  • 集計処理のテストなど、テストデータの物量が必要になってくる場合に、オススメ

補足

2016-12-11 追記:
コメントでもご指摘を頂いておりますが、テストでトランザクションを使用して、挿入したテストデータをロールバックしようとした場合、DbSetup では、これが一部で上手く動作しません。少し手を加えれば、DbSetup でもテストデータのロールバックを扱うことができますが、提供されたデフォルトのテスト機能と方法を使用した場合、DbSetup ではこれが動作しない点には注意が必要となります。
詳しくは以下をご確認ください。
Spring Boot, Spock, DBUnit でトランザクションを使ったテスト #DbSetup でのトランザクション
Spring Boot, Spock, DbSetup でトランザクションを使ったテスト

30
33
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
33