CLOVER🍀

That was when it all began.

DbUnitを詊しおみる

これは、なにをしたくお曞いたもの

Javaのテストたわりの情報を芋おいお、デヌタベヌスの事情っお今はどうなんだろうず思いたしお。

芋おいお、DbUnitから倉わっおいなさそうだったのず、そもそもDbUnitを䜿ったこずがなかったのでちょっず詊しおみるこずにしたした。

DbUnitず呚蟺事情

DbUnitはJUnitのExtensionずされおいお、デヌタベヌスドリブンなプロゞェクトをタヌゲットにしおいたす。

DbUnit is a JUnit extension (also usable with Ant) targeted at database-driven projects that, among other things, puts your database into a known state between test runs.

About DbUnit

デヌタの゚クスポヌト・むンポヌトの機胜を持ち、デヌタベヌスが持぀倀でのアサヌションも行えたす。

DbUnit has the ability to export and import your database data to and from XML datasets. Since version 2.0, DbUnit can also work with very large datasets when used in streaming mode. DbUnit can also help you to verify that your database data match an expected set of values.

Spring Frameworkのテストに関するドキュメント内でも、デヌタベヌスに関するテストのリ゜ヌスで参照されおいるのは、DbUnitのみです。

DbUnit: JUnit extension (also usable with Ant and Maven) that is targeted at database-driven projects and, among other things, puts your database into a known state between test runs.

Testing / Further Resources

なので、たずはDbUnitを芋おみるのが劥圓なのかな、ずは思うのですが。

DbUnitのサむトを芋おみるず、最終リリヌスが2012幎の2.4.9になっおいおSourceForgeにあるプロゞェクトも曎新されおいたせん。

dbUnit download | SourceForge.net

が、よく芋るずMaven Centralからは新しいバヌゞョンがダりンロヌドできるず曞かれおいるので、芋おみるず珟時点の最新のバヌゞョンは
2.7.3です。

https://search.maven.org/artifact/org.dbunit/dbunit/2.7.3/jar

珟圚、゜ヌスコヌドはこちらでバヌゞョン管理されおいるようです。

dbUnit / dbunit / [d13768]

なので、メンテナンスは継続されおいそうです。

远蚘 

珟圚のDbUnitのWebサむトずしおはこちらを芋るのがよさそうです。

DbUnit – About DbUnit

その他、DbUnitを取り巻くOSSずしお、Spring Test向けのSpring Test DBUnitずいうものがありたすが、こちらは曎新が止たっお
久しそう 。

Spring Test DBUnit – Introduction

Database Riderずいうものもあるので、今はこちらを芋るのがよいのかもしれたせん。

Database Rider

DbUnitに぀いお

DbUnitに圱響を䞎えた、デヌタベヌスのテストに関する話はこちら。

Database Testing

いわゆるGetting Started。

Getting Started

ステップずしおは、以䞋のようです。

  • デヌタファむルの䜜成
  • デヌタベヌスの接続情報等のセットアップを行い、テストを実斜

DbUnitはDBTestCaseクラスを提䟛しおいるようですが、JUnit 5時代のものではないのでこちらは䜿わないでしょうね。

実際、DbUnitの䟝存関係にはJUnit 5は含たれおいたせん。

https://sourceforge.net/p/dbunit/code.git/ci/dbunit-2.7.3/tree/pom.xml

アサヌションのサポヌトずしおは、以䞋あたりができそうです。

  • ク゚リヌを元にしたスナップショットITableの取埗
  • スナップショットの比范の際に、カラムのフィルタリング
  • スナップショットの゜ヌト

たた、デヌタファむルからのデヌタロヌドも可胜なようです。

DbUnitを構成する䞻なコンポヌネントは、こちらに曞かれおいたす。

Core Components

  • IDatabaseConnectionむンタヌフェヌス 
 デヌタベヌスぞの接続を衚す
  • IDataSetむンタヌフェヌス 
 テヌブルのコレクションを衚す
  • DatabaseOperationクラス 
 各テストの前埌でデヌタベヌスに察する操䜜を抜象化する

IDataSetむンタヌフェヌスの実装ずしお、XML、ク゚リヌ、Excel、プログラムで組み立おるもの等いろいろあるようですが、
ドキュメントに蚘茉されおいるものは叀そうなので、゜ヌスコヌドを芋た方がいいんでしょうね。

https://sourceforge.net/p/dbunit/code.git/ci/dbunit-2.7.3/tree/src/main/java/org/dbunit/dataset/

CSVファむルでも扱えそうです。

DatabaseOperationクラスは、各テストの前埌でデヌタベヌスに察する操䜜を実行したすが、以䞋のようなこずを行えたす。

  • UPDATE
  • INSERT
  • DELETE
  • DELETE_ALL
  • TRUNCATE
  • REFRESH
  • CLEAN_INSERT
  • NONE

盎感的にわからなそうなのはREFRESHずCLEAN_INSERTでしょうか。

REFRESHは察象ずなるデヌタセットをデヌタベヌスに反映するもので、デヌタセットに含たれる範囲のデヌタのみを察象ずし、
それ以倖のデヌタには圱響を䞎えたせん。内郚的には、updateしお曎新されなければinsertしようずしたす。
CLEANはDELETE_ALLの埌にINSERTを実行するものです。

その他、耇数の操䜜をひず぀にたずめるCompositeOperationクラス、トランザクション内で操䜜を実行するTransactionOperationクラスなども
ありたす。

ずたあ、ドキュメントを読むのはこれくらいにしお、実際に䜿っおいっおみたしょう。

お題

今回、お題は以䞋にしたす。

  • DbUnitを䜿ったテストを曞く
  • ファむルで甚意するデヌタセットには、XMLを䜿甚する
  • デヌタベヌスアクセスにはDoma 2を䜿甚する
  • テスティングフレヌムワヌクには、JUnit 5を䜿甚する
  • デヌタベヌスにはMySQL 8.0を䜿甚する

デヌタセットで䜿うファむルはCSVにしようかなず思っおいたのですが、CSVでのデヌタセットはなかなか独特みたいなので、今回はXMLに
したした。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.2 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.2+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.2, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-107-generic", arch: "amd64", family: "unix"

MySQLは172.17.0.2で動䜜しおいるものずしたす。バヌゞョンは、こちら。

$ mysql --version
mysql  Ver 8.0.28 for Linux on x86_64 (MySQL Community Server - GPL)

準備

たずは、プロゞェクトを䜜成しおいきたす。

今回のMaven䟝存関係等はこちら。

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.seasar.doma</groupId>
            <artifactId>doma-core</artifactId>
            <version>2.51.0</version>
        </dependency>

        <dependency>
            <groupId>org.seasar.doma</groupId>
            <artifactId>doma-processor</artifactId>
            <version>2.51.0</version>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.dbunit</groupId>
            <artifactId>dbunit</artifactId>
            <version>2.7.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

DbUnitは2.7.3ですね。

続いお、デヌタベヌスアクセスを行うプログラムをDoma 2を䜿っお䜜成したす。

゚ンティティクラス。

src/main/java/org/littlewings/dbunit/Book.java

package org.littlewings.dbunit;

import java.time.LocalDate;

import org.seasar.doma.Entity;
import org.seasar.doma.Id;

@Entity
public class Book {
    @Id
    String isbn;
    String title;
    Integer price;
    LocalDate publishDate;
    String category;

    public static Book create(String isbn, String title, Integer price, LocalDate publishDate, String category) {
        Book book = new Book();
        book.setIsbn(isbn);
        book.setTitle(title);
        book.setPrice(price);
        book.setCategory(category);

        book.setPublishDate(publishDate);

        return book;
    }

    // gettersetterは省略
}

Dao。

src/main/java/org/littlewings/dbunit/BookDao.java

package org.littlewings.dbunit;

import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.Script;
import org.seasar.doma.Select;
import org.seasar.doma.Sql;

@Dao
public interface BookDao {
    @Sql("""
 drop table if exists book;
 create table book(
   isbn varchar(14),
   title varchar(100),
   price int,
   publish_date date,
   category varchar(20),
   primary key(isbn)
 )
 """)
    @Script
    void createTableIfExistsRecreate();

    @Sql("truncate table book")
    @Script
    void truncate();

    @Insert
    int insert(Book book);

    @Sql("select /*%expand*/* from book where isbn = /* isbn */'dummy'")
    @Select
    Book selectByIsbn(String isbn);
}

Config。

src/main/java/org/littlewings/dbunit/DomaConfig.java

package org.littlewings.dbunit;

import javax.sql.DataSource;

import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.Naming;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.MysqlDialect;
import org.seasar.doma.jdbc.tx.LocalTransactionDataSource;
import org.seasar.doma.jdbc.tx.LocalTransactionManager;
import org.seasar.doma.jdbc.tx.TransactionManager;

public class DomaConfig implements Config {
    private static final DomaConfig CONFIG = new DomaConfig();

    Dialect dialect;

    LocalTransactionDataSource dataSource;

    TransactionManager transactionManager;

    public static DomaConfig singleton() {
        return CONFIG;
    }

    private DomaConfig() {
        dialect = new MysqlDialect();
        dataSource = new LocalTransactionDataSource(
                "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin",
                "kazuhira",
                "password"
        );
        transactionManager = new LocalTransactionManager(dataSource.getLocalTransaction(getJdbcLogger()));
    }

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }

    @Override
    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    @Override
    public Naming getNaming() {
        return Naming.SNAKE_LOWER_CASE;
    }
}

ここたでで、準備は完了です。あずはこれらを䜿っおテストコヌドを曞いおいきたす。

DbUnitを䜿っおみる

では、DbUnitを䜿っおいきたしょう。たずは、テストコヌドの雛圢を甚意。

src/test/java/org/littlewings/dbunit/DbUnitGettingStartedTest.java

package org.littlewings.dbunit;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;

import org.dbunit.Assertion;
import org.dbunit.DatabaseUnitException;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.SortedTable;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.ext.mysql.MySqlConnection;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class DbUnitGettingStartedTest {
    @BeforeAll
    public static void setUpAll() {
        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());
        DomaConfig
                .singleton()
                .getTransactionManager()
                .required(() -> bookDao.createTableIfExistsRecreate());
    }

    @BeforeEach
    public void setUp() {
        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());
        DomaConfig
                .singleton()
                .getTransactionManager()
                .required(() -> bookDao.truncate());
    }

    // ここにテストを曞く
}

テスト前に1床だけテヌブルをdrop  createしお、各テストごずにテヌブルをtruncateしたす。

最初はDbUnitを䜿っおデヌタベヌスに接続しおみたしょう。こんな感じで、JDBCのConnectionからDatabaseConnectionを取埗したす。

    @Test
    public void createDbUnitConnection() throws DatabaseUnitException, SQLException {
        DatabaseConnection databaseConnection = null;
        try (Connection conn = DriverManager
                .getConnection(
                        "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin",
                        "kazuhira",
                        "password");) {
            databaseConnection = new MySqlConnection(conn, null);
        } finally {
            if (databaseConnection != null) {
                databaseConnection.close();
            }
        }
    }

今回はMySqlConnectionクラスを䜿っおいたすが、汎甚的なDatabaseConnectionクラスをnewしおも良いですし、他のデヌタベヌスの堎合は
それぞれに応じたクラスを䜿っおもよいでしょう。

https://sourceforge.net/p/dbunit/code.git/ci/dbunit-2.7.3/tree/src/main/java/org/dbunit/ext/

芋たずころ、汎甚的なDatabaseConnectionクラスずの差はそれぞれのデヌタベヌスを考慮したデヌタ型やメタデヌタを扱うクラスを適甚しお
くれるずころみたいですね。

次に、アサヌションを行っおみたす。

    @Test
    public void firstAssertion() throws DatabaseUnitException, SQLException {
        DatabaseConnection databaseConnection = null;
        try (Connection conn = DriverManager
                .getConnection(
                        "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin",
                        "kazuhira",
                        "password");) {
            databaseConnection = new MySqlConnection(conn, null);

            DomaConfig
                    .singleton()
                    .getTransactionManager()
                    .required(() -> {
                        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());

                        List<Book> books = List.of(
                                Book.create("978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", 2860, LocalDate.of(2020, 3, 13), "java"),
                                Book.create("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), "java"),
                                Book.create("978-4774189093", "Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, LocalDate.of(2017, 4, 18), "java"),
                                Book.create("978-4798161488", "MySQL培底入門 第4版 MySQL 8.0察応", 4180, LocalDate.of(2020, 7, 6), "mysql"),
                                Book.create("978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960, LocalDate.of(2016, 8, 26), "mysql")
                        );

                        books.forEach(bookDao::insert);
                    });

            IDataSet actualDataSet = databaseConnection.createDataSet();
            ITable actualTable = actualDataSet.getTable("book");

            IDataSet expectedDataSet =
                    new FlatXmlDataSetBuilder()
                            .build(getClass().getClassLoader().getResourceAsStream("DbUnitGettingStartedTest.firstAssertion.xml"));
            ITable expectedTable = expectedDataSet.getTable("book");

            // assert dataset
            Assertion.assertEquals(expectedDataSet, actualDataSet);
            // assert table
            Assertion.assertEquals(expectedTable, actualTable);
        } finally {
            if (databaseConnection != null) {
                databaseConnection.close();
            }
        }
    }

テヌブルにデヌタを登録。

            DomaConfig
                    .singleton()
                    .getTransactionManager()
                    .required(() -> {
                        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());

                        List<Book> books = List.of(
                                Book.create("978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", 2860, LocalDate.of(2020, 3, 13), "java"),
                                Book.create("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), "java"),
                                Book.create("978-4774189093", "Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, LocalDate.of(2017, 4, 18), "java"),
                                Book.create("978-4798161488", "MySQL培底入門 第4版 MySQL 8.0察応", 4180, LocalDate.of(2020, 7, 6), "mysql"),
                                Book.create("978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960, LocalDate.of(2016, 8, 26), "mysql")
                        );

                        books.forEach(bookDao::insert);
                    });

この埌、DatabaseConnectionクラスのむンスタンスからデヌタセットおよびテヌブルのデヌタを取埗したす。

            IDataSet actualDataSet = databaseConnection.createDataSet();
            ITable actualTable = actualDataSet.getTable("book");

そしお、確認を行うデヌタセットをXMLファむルから読み蟌みたす。こちらもデヌタセットず、デヌタセットからテヌブルに察応する
デヌタずしお扱えたす。

            IDataSet expectedDataSet =
                    new FlatXmlDataSetBuilder()
                            .build(getClass().getClassLoader().getResourceAsStream("DbUnitGettingStartedTest.firstAssertion.xml"));
            ITable expectedTable = expectedDataSet.getTable("book");

甚意したXMLファむルはこちら。

src/test/resources/DbUnitGettingStartedTest.firstAssertion.xml

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <book isbn="978-4295008477" title="新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]" price="2860" publish_date="2020-03-13" category="java"/>
    <book isbn="978-4621303252" title="Effective Java 第3版" price="4400" publish_date="2018-10-30" category="java"/>
    <book isbn="978-4774189093" title="Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで" price="3278" publish_date="2017-04-18" category="java"/>
    <book isbn="978-4798147406" title="詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド" price="3960" publish_date="2016-08-26" category="mysql"/>
    <book isbn="978-4798161488" title="MySQL培底入門 第4版 MySQL 8.0察応" price="4180" publish_date="2020-07-06" category="mysql"/>
</dataset>

芁玠名にテヌブル名、属性にカラム名ずいった感じですね。

その他のファむル圢匏の堎合は、リポゞトリのテストに関する郚分を芋るずよいでしょう。

https://sourceforge.net/p/dbunit/code.git/ci/dbunit-2.7.3/tree/src/test/resources/

https://sourceforge.net/p/dbunit/code.git/ci/dbunit-2.7.3/tree/src/test/java/org/dbunit/dataset/

アサヌション。デヌタセット単䜍でも行えたすし、テヌブル単䜍でも行えたす。

            // assert dataset
            Assertion.assertEquals(expectedDataSet, actualDataSet);
            // assert table
            Assertion.assertEquals(expectedTable, actualTable);

こちらで、珟圚デヌタセットおよびテヌブル今回はデヌタセットずテヌブルが同じ範囲ですがに栌玍されおいるデヌタず、XMLファむルで
甚意したデヌタが等しいこずが確認できたした。

アサヌションを行う察象のデヌタを絞り蟌むこずもできたす。たずえば、SQLを䜿う堎合はこちら。

    @Test
    public void createDataSetFromQuery() throws DatabaseUnitException, SQLException {
        DatabaseConnection databaseConnection = null;
        try (Connection conn = DriverManager
                .getConnection(
                        "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin",
                        "kazuhira",
                        "password");) {
            databaseConnection = new MySqlConnection(conn, null);

            DomaConfig
                    .singleton()
                    .getTransactionManager()
                    .required(() -> {
                        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());

                        List<Book> books = List.of(
                                Book.create("978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", 2860, LocalDate.of(2020, 3, 13), "java"),
                                Book.create("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), "java"),
                                Book.create("978-4774189093", "Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, LocalDate.of(2017, 4, 18), "java"),
                                Book.create("978-4798161488", "MySQL培底入門 第4版 MySQL 8.0察応", 4180, LocalDate.of(2020, 7, 6), "mysql"),
                                Book.create("978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960, LocalDate.of(2016, 8, 26), "mysql")
                        );

                        books.forEach(bookDao::insert);
                    });

            ITable actualTable =
                    databaseConnection
                            .createQueryTable("query_result", "select * from book where category = 'mysql' order by isbn asc");

            IDataSet expectedDataSet =
                    new FlatXmlDataSetBuilder()
                            .build(getClass().getClassLoader().getResourceAsStream("DbUnitGettingStartedTest.createDataSetFromQuery.xml"));
            ITable expectedTable = expectedDataSet.getTable("book");

            Assertion.assertEquals(expectedTable, actualTable);
        } finally {
            if (databaseConnection != null) {
                databaseConnection.close();
            }
        }
    }

この郚分ですね。先ほどの堎合はデヌタセットずしお遞択した範囲および、テヌブルに関しおはそのテヌブルの党デヌタが察象になりたすが、
今回はカテゎリヌがmysqlのものに絞っおいたす。

            ITable actualTable =
                    databaseConnection
                            .createQueryTable("query_result", "select * from book where category = 'mysql' order by isbn asc");

この堎合はテヌブルのデヌタずしお扱うこずになるので、アサヌションもテヌブル単䜍になりたす。

            IDataSet expectedDataSet =
                    new FlatXmlDataSetBuilder()
                            .build(getClass().getClassLoader().getResourceAsStream("DbUnitGettingStartedTest.createDataSetFromQuery.xml"));
            ITable expectedTable = expectedDataSet.getTable("book");

            Assertion.assertEquals(expectedTable, actualTable);

甚意したXMLファむルはこちら。カテゎリヌがmysqlずなっおいるもののみになりたすね。

src/test/resources/DbUnitGettingStartedTest.createDataSetFromQuery.xml

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <book isbn="978-4798147406" title="詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド" price="3960" publish_date="2016-08-26" category="mysql"/>
    <book isbn="978-4798161488" title="MySQL培底入門 第4版 MySQL 8.0察応" price="4180" publish_date="2020-07-06" category="mysql"/>
</dataset>

デヌタセット内のデヌタは、䞻キヌにより゜ヌトされたす。この゜ヌトキヌを倉曎するには、SortedTableクラスを䜿いたす。

    @Test
    public void sortTable() throws DatabaseUnitException, SQLException {
        DatabaseConnection databaseConnection = null;
        try (Connection conn = DriverManager
                .getConnection(
                        "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin",
                        "kazuhira",
                        "password");) {
            databaseConnection = new MySqlConnection(conn, null);

            DomaConfig
                    .singleton()
                    .getTransactionManager()
                    .required(() -> {
                        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());

                        List<Book> books = List.of(
                                Book.create("978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", 2860, LocalDate.of(2020, 3, 13), "java"),
                                Book.create("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), "java"),
                                Book.create("978-4774189093", "Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, LocalDate.of(2017, 4, 18), "java"),
                                Book.create("978-4798161488", "MySQL培底入門 第4版 MySQL 8.0察応", 4180, LocalDate.of(2020, 7, 6), "mysql"),
                                Book.create("978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960, LocalDate.of(2016, 8, 26), "mysql")
                        );

                        books.forEach(bookDao::insert);
                    });

            IDataSet actualDataSet = databaseConnection.createDataSet();
            SortedTable actualTable = new SortedTable(actualDataSet.getTable("book"), new String[]{"price"});
            actualTable.setUseComparable(true);

            IDataSet expectedDataSet =
                    new FlatXmlDataSetBuilder()
                            .build(getClass().getClassLoader().getResourceAsStream("DbUnitGettingStartedTest.sortTable.xml"));
            ITable expectedTable = expectedDataSet.getTable("book");
            //SortedTable expectedTable = new SortedTable(expectedDataSet.getTable("book"), new String[]{"price"});
            //expectedTable.setUseComparable(true);

            Assertion.assertEquals(expectedTable, actualTable);
        } finally {
            if (databaseConnection != null) {
                databaseConnection.close();
            }
        }
    }

こんな感じで、テヌブルをさらにSortedTableクラスでラップしたす。SortedTableクラスのむンスタンス䜜成埌に、

SortedTable#setUseComparableメ゜ッドを呌び出す必芁があるようです。

            IDataSet actualDataSet = databaseConnection.createDataSet();
            SortedTable actualTable = new SortedTable(actualDataSet.getTable("book"), new String[]{"price"});
            actualTable.setUseComparable(true);

今回は、䟡栌で゜ヌトしたした。゜ヌト順の指定はできないようです。ずいうか、゜ヌトを行っおいるのはデヌタベヌス䞊ではなく
Java䞊でですね。

https://sourceforge.net/p/dbunit/code.git/ci/dbunit-2.7.3/tree/src/main/java/org/dbunit/dataset/SortedTable.java

甚意したXMLファむルは、こちら。あらかじめ䟡栌で゜ヌトしおおきたした。

src/test/resources/DbUnitGettingStartedTest.sortTable.xml

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <book isbn="978-4295008477" title="新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]" price="2860" publish_date="2020-03-13" category="java"/>
    <book isbn="978-4774189093" title="Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで" price="3278" publish_date="2017-04-18" category="java"/>
    <book isbn="978-4798147406" title="詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド" price="3960" publish_date="2016-08-26" category="mysql"/>
    <book isbn="978-4798161488" title="MySQL培底入門 第4版 MySQL 8.0察応" price="4180" publish_date="2020-07-06" category="mysql"/>
    <book isbn="978-4621303252" title="Effective Java 第3版" price="4400" publish_date="2018-10-30" category="java"/>
</dataset>

よっおこちらは゜ヌトしなくおもよいのですが、゜ヌトする堎合はコメントアりトしおいる箇所のような感じになりたす。

            ITable expectedTable = expectedDataSet.getTable("book");
            //SortedTable expectedTable = new SortedTable(expectedDataSet.getTable("book"), new String[]{"price"});
            //expectedTable.setUseComparable(true);

アサヌション時に、デヌタの゜ヌト結果が合わないずテストが倱敗するので泚意したしょう。

DatabaseOperationクラスを䜿っおみる

最埌に、DatabaseOperationクラスを䜿っおみたしょう。このクラスを䜿うず、テスト前埌で行いたいようなデヌタセットの操䜜を簡単に
行えたす。

ただ、今回はテスト前埌で実行するのではなく、テストコヌド内でアサヌションず組み合わせおDatabaseOperationクラスの機胜自䜓を
確認しおいこうかなず思いたす。

最初にテストコヌドを䞞ごず茉せおしたいたしょう。

src/test/java/org/littlewings/dbunit/DbUnitOperationTest.java

package org.littlewings.dbunit;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;

import org.dbunit.Assertion;
import org.dbunit.DatabaseUnitException;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.ext.mysql.MySqlConnection;
import org.dbunit.operation.DatabaseOperation;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class DbUnitOperationTest {
    @BeforeAll
    public static void setUpAll() {
        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());
        DomaConfig
                .singleton()
                .getTransactionManager()
                .required(() -> bookDao.createTableIfExistsRecreate());
    }

    @Test
    public void test() throws SQLException, DatabaseUnitException {
        DatabaseConnection databaseConnection = null;
        try (Connection conn = DriverManager
                .getConnection(
                        "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&characterSetResults=utf-8&connectionCollation=utf8mb4_0900_bin",
                        "kazuhira",
                        "password");) {
            databaseConnection = new MySqlConnection(conn, null);

            // INSERT Operation
            IDataSet insertedDataSet =
                    new FlatXmlDataSetBuilder()
                            .build(getClass().getClassLoader().getResourceAsStream("DbUnitOperationTest.test.xml"));

            DatabaseOperation.INSERT.execute(databaseConnection, insertedDataSet);
            // JUnit 5 Assersions
            Assertions.assertEquals(2, databaseConnection.getRowCount("book"));

            DomaConfig
                    .singleton()
                    .getTransactionManager()
                    .required(() -> {
                        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());

                        List<Book> books = List.of(
                                Book.create("978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", 2860, LocalDate.of(2020, 3, 13), "java"),
                                Book.create("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), "java"),
                                Book.create("978-4774189093", "Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, LocalDate.of(2017, 4, 18), "java"),
                                Book.create("978-4798161488", "MySQL培底入門 第4版 MySQL 8.0察応", 4180, LocalDate.of(2020, 7, 6), "mysql"),
                                Book.create("978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960, LocalDate.of(2016, 8, 26), "mysql")
                        );

                        books.forEach(bookDao::insert);
                    });

            Assertions.assertEquals(7, databaseConnection.getRowCount("book"));

            // REFRESHupdate or insert Operation
            IDataSet refreshedDataSet =
                    new FlatXmlDataSetBuilder()
                            .build(getClass().getClassLoader().getResourceAsStream("DbUnitOperationTest.test.xml"));

            DatabaseOperation.REFRESH.execute(databaseConnection, refreshedDataSet);
            // JUnit 5 Assersions
            Assertions.assertEquals(7, databaseConnection.getRowCount("book"));
            ITable categories = databaseConnection.createQueryTable("result", "select category from book group by category order by category");
            Assertions.assertEquals("java", categories.getValue(0, "category"));
            Assertions.assertEquals("linux", categories.getValue(1, "category"));
            Assertions.assertEquals("mysql", categories.getValue(2, "category"));

            // CLEAN_INSERTdelete all  insert Operation
            DatabaseOperation.CLEAN_INSERT.execute(databaseConnection, insertedDataSet);

            IDataSet actualDataSet = databaseConnection.createDataSet();

            // JUnit 5 Assersions
            Assertions.assertEquals(2, databaseConnection.getRowCount("book"));
            // DbUnit Assertion
            Assertion.assertEquals(insertedDataSet, actualDataSet);
        } finally {
            if (databaseConnection != null) {
                databaseConnection.close();
            }
        }
    }
}

先ほどのテストコヌドず同様、1回目にテヌブルをdrop  createするこずにしたす。

    @BeforeAll
    public static void setUpAll() {
        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());
        DomaConfig
                .singleton()
                .getTransactionManager()
                .required(() -> bookDao.createTableIfExistsRecreate());
    }

XMLファむルでのデヌタセットも甚意。

src/test/resources/DbUnitOperationTest.test.xml

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <book isbn="978-4797397642" title="本気で孊ぶ Linux実践入門 サヌバ運甚のための業務レベル管理術" price="3278" publish_date="2019-05-30" category="linux"/>
    <book isbn="978-4798155760" title="Ubuntuサヌバヌ培底入門" price="4180" publish_date="2018-06-13" category="linux"/>
</dataset>

DatabaseOperationクラスを䜿ったデヌタセットの操䜜に絞っお芋おいきたす。

たずは、DatabaseOperation.INSERTで甚意したXMLファむルでのデヌタセットの内容をinsertしたす。

            // INSERT Operation
            IDataSet insertedDataSet =
                    new FlatXmlDataSetBuilder()
                            .build(getClass().getClassLoader().getResourceAsStream("DbUnitOperationTest.test.xml"));

            DatabaseOperation.INSERT.execute(databaseConnection, insertedDataSet);
            // JUnit 5 Assersions
            Assertions.assertEquals(2, databaseConnection.getRowCount("book"));

DatabaseConnection#getRowCountを䜿うず、察象のテヌブルのレコヌド数を取埗できたす。

ここでは、DatabaseOperation.INSERT#executeメ゜ッド実行埌に、甚意したXMLファむルに含たれるデヌタ件数、぀たり2レコヌドが
存圚しおいるこずが確認できたした。

次に、Doma 2を䜿っおデヌタを登録。

            DomaConfig
                    .singleton()
                    .getTransactionManager()
                    .required(() -> {
                        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());

                        List<Book> books = List.of(
                                Book.create("978-4295008477", "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]", 2860, LocalDate.of(2020, 3, 13), "java"),
                                Book.create("978-4621303252", "Effective Java 第3版", 4400, LocalDate.of(2018, 10, 30), "java"),
                                Book.create("978-4774189093", "Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, LocalDate.of(2017, 4, 18), "java"),
                                Book.create("978-4798161488", "MySQL培底入門 第4版 MySQL 8.0察応", 4180, LocalDate.of(2020, 7, 6), "mysql"),
                                Book.create("978-4798147406", "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド", 3960, LocalDate.of(2016, 8, 26), "mysql")
                        );

                        books.forEach(bookDao::insert);
                    });

            Assertions.assertEquals(7, databaseConnection.getRowCount("book"));

テヌブル内には7件のデヌタがあるこずが確認できたす。

DatabaseOperation.REFRESHを䜿うず、察象のデヌタセットを䜿っおupdate、デヌタがなければinsertを行いたす。

            // REFRESHupdate or insert Operation
            IDataSet refreshedDataSet =
                    new FlatXmlDataSetBuilder()
                            .build(getClass().getClassLoader().getResourceAsStream("DbUnitOperationTest.test.xml"));

            DatabaseOperation.REFRESH.execute(databaseConnection, refreshedDataSet);
            // JUnit 5 Assersions
            Assertions.assertEquals(7, databaseConnection.getRowCount("book"));
            ITable categories = databaseConnection.createQueryTable("result", "select category from book group by category order by category");
            Assertions.assertEquals("java", categories.getValue(0, "category"));
            Assertions.assertEquals("linux", categories.getValue(1, "category"));
            Assertions.assertEquals("mysql", categories.getValue(2, "category"));

よっお、デヌタの数は倉わっおいたせん。

最埌にDatabaseOperation.CLEAN_INSERT。こちらは察象のデヌタセットの範囲を党件削陀した埌、指定のデヌタセットのデヌタを
登録したす。

            // CLEAN_INSERTdelete all  insert Operation
            DatabaseOperation.CLEAN_INSERT.execute(databaseConnection, insertedDataSet);

            IDataSet actualDataSet = databaseConnection.createDataSet();

            // JUnit 5 Assersions
            Assertions.assertEquals(2, databaseConnection.getRowCount("book"));
            // DbUnit Assertion
            Assertion.assertEquals(insertedDataSet, actualDataSet);

よっお、今回Doma 2を䜿っお登録したデヌタは削陀され、XMLファむルで甚意した2件だけが残っおいる状態になりたす。

これらの操䜜は、このように独立したクラスやその組み合わせで実珟されおいたす。

    /** @see DummyOperation */
    public static final DatabaseOperation NONE = new DummyOperation();
    /** @see UpdateOperation */
    public static final DatabaseOperation UPDATE = new UpdateOperation();
    /** @see InsertOperation */
    public static final DatabaseOperation INSERT = new InsertOperation();
    /** @see RefreshOperation */
    public static final DatabaseOperation REFRESH = new RefreshOperation();
    /** @see DeleteOperation */
    public static final DatabaseOperation DELETE = new DeleteOperation();
    /** @see DeleteAllOperation */
    public static final DatabaseOperation DELETE_ALL = new DeleteAllOperation();
    /** @see TruncateTableOperation */
    public static final DatabaseOperation TRUNCATE_TABLE = new TruncateTableOperation();
    /**
     * @see DeleteAllOperation
     * @see InsertOperation
     * @see CompositeOperation
     */
    public static final DatabaseOperation CLEAN_INSERT = new CompositeOperation(
            DELETE_ALL, INSERT);

https://sourceforge.net/p/dbunit/code.git/ci/dbunit-2.7.3/tree/src/main/java/org/dbunit/operation/DatabaseOperation.java

各DatabaseOperationクラスの実装が気になるのであれば、ドキュメントの説明および゜ヌスコヌドを芋るず良いでしょう。

Core Components / DatabaseOperation

https://sourceforge.net/p/dbunit/code.git/ci/dbunit-2.7.3/tree/src/main/java/org/dbunit/operation/

その他

今回は扱いたせんでした、IDatabaseTesterむンタヌフェヌスずいうここたで芋おきたような操䜜をある皋床たずめられるクラスも
存圚したす。実装ずしおはJdbcDatabaseTesterクラスやDataSourceDatabaseTesterクラスなどを䜿うのかなず思いたす。

Getting Started

本来はDBTestCaseクラスを継承できない堎合に䜿うようなのですが、DBTestCaseクラスは叀いJUnit向けのものなので、珟圚は
䜿うこずはないでしょう。

IDatabaseTesterむンタヌフェヌスの実装クラスを䜿うか、自分で䜜成したりしおこのあたりの凊理はうたく扱えるように考えお
いくんでしょうね。

たずめ

DbUnitを詊しおみたした。

最初はWebサむトを芋お「曎新されおいないのかな」ず思ったのですが、珟圚もJUnit 5には察応しおいないずはいえメンテナンスは
続いおいるようですし、䜿い方を抌さえおおいおもよいかなず思いたした。

今回は、こんなずころで。