CLOVER🍀

That was when it all began.

Database Riderを詊しおみる

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

以前、DbUnitを䜿っお゚ントリヌを曞いおみたした。

DbUnitを試してみる - CLOVER🍀

この時にも少し觊れおいたのですが、今回はDatabase Riderずいうものを詊しおみたいず思いたす。

Database Rider

Database Riderは、DbUnitずJUnitを統合しおデヌタベヌスのテストをしやすくするものです。

Database Rider integrates DBUnit and JUnit in order to make database testing a breeze!

Database Rider

機胜ずしおは、以䞋のようですね。

ドキュメントは、以䞋から最新のものを参照したす。

Document

珟時点で公開されおいる最新のドキュメントは、1.35.0のようです䞀芧にはありたせんでしたが、URLからバヌゞョン指定するず参照
できたした。

Database Rider Documentation

Getting Startedはこちら。

Database Rider Getting Started

GitHubリポゞトリはこちら。

GitHub - database-rider/database-rider: Database testing made easy!

サンプルは、GitHubリポゞトリに含たれおいたす。

https://github.com/database-rider/database-rider/tree/1.35.0/rider-examples

以䞋のようなサンプルがありたす。

  • dbunit-tomee-appcomposer-sample
  • jOOQ-DBUnit-flyway-example
  • jpa-productivity-boosters
  • quarkus-dbunit-sample
  • quarkus-postgres-sample
  • rider-kotlin
  • spring-boot-dbunit-sample

ずりあえずこれくらいにしお、䜿っおいっおみたしょう。

お題

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

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

参考にするドキュメントは、たずはGetting Startedにしたいず思いたす。

Database Rider Getting Started

ドキュメント本䜓の方も、必芁に応じお参照したす。

Database Rider Documentation

環境

今回の環境は、こちら。

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


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.4, 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-131-generic", arch: "amd64", family: "unix"

MySQLは、172.17.0.2で動䜜しおいるものずしたす。

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


mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.31    |
+-----------+
1 row in set (0.00 sec)

デヌタベヌスはpractice、アカりントはkazuhirapasswordで䜜成しおいるものずしたす。

準備

Maven䟝存関係など。

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.0.31</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.seasar.doma</groupId>
            <artifactId>doma-core</artifactId>
            <version>2.53.1</version>
        </dependency>

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

        <dependency>
            <groupId>com.github.database-rider</groupId>
            <artifactId>rider-junit5</artifactId>
            <version>1.35.0</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.23.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

ポむントは、こちらですね。

        <dependency>
            <groupId>com.github.database-rider</groupId>
            <artifactId>rider-junit5</artifactId>
            <version>1.35.0</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

Database Riderのアヌティファクトのうち、JUnit 5向けのrider-junit5を䜿っおいるずころですね。

䟝存しおいるJUnit 5が叀かったので、excludeしおJUnit 5は自分で远加したした。

テヌブルは、以䞋の定矩で䜜成。

create table book(
  isbn varchar(14),
  title varchar(100),
  price int,
  publish_date date,
  category varchar(20),
  primary key(isbn)
);

続いお、Doma 2を䜿ったプログラムを曞いおいきたす。

゚ンティティ。

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

package org.littlewings.databaserider;

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.setPublishDate(publishDate);
        book.setCategory(category);

        return book;
    }

    // gettersetterは省略
}

Dao。

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

package org.littlewings.databaserider;

import java.util.List;

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

@Dao
public interface BookDao {
    @Insert
    int insert(Book book);

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

    @Sql("select /*%expand*/* from book order by price desc")
    @Select
    List<Book> selectAllOrderByPriceDesc();

    @Delete
    int delete(Book book);
}

Config。

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

package org.littlewings.databaserider;

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&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;
    }
}

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

Database Riderを䜿っおみる

では、Database Riderを䜿っおみたしょう。

たずは、テストコヌドの雛圢を甚意。

src/test/java/org/littlewings/databaserider/DatabaseRiderGettingStartedTest.java

package org.littlewings.databaserider;

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

import com.github.database.rider.core.api.connection.ConnectionHolder;
import com.github.database.rider.core.api.dataset.DataSet;
import com.github.database.rider.core.api.dataset.DataSetFormat;
import com.github.database.rider.core.api.dataset.ExpectedDataSet;
import com.github.database.rider.core.api.exporter.ExportDataSet;
import com.github.database.rider.junit5.DBUnitExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(DBUnitExtension.class)
public class DatabaseRiderGettingStartedTest {

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

こちらを芋ながら進めおいきたす。

Database Rider Getting Started

先に、JUnit 5向けの蚭定から。

@ExtendWith(DBUnitExtension.class)
public class DatabaseRiderGettingStartedTest {

Database Rider Getting Started / Riding database in JUnit 5 tests

Getting Startedなどを芋おいるず@RunWith(JUnitPlatform.class)が芁るず曞いおいたしたが、こちらはなくおも良さそうですけどね。

なお、@DBRiderずいうアノテヌションを䜿っおも良さそうです䞭身は@DBUnitExtensionず@Testの合成。

import com.github.database.rider.junit5.api.DBRider;

@DBRider
public class DatabaseRiderGettingStartedTest {

デヌタの䟋。

Database Rider Getting Started / Example

今回は、こんなデヌタを甚意したした。

src/test/resources/dataset/books.yml

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

こちらを、@DataSetアノテヌションで指定したす。クラスパス䞊のファむルを指定する、で良さそうです。

    @Test
    @DataSet("dataset/books.yml")
    public void gettingStarted() {
        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());

        DomaConfig
                .singleton()
                .getTransactionManager()
                .required(() -> {
                    Book mysqlBook = bookDao.selectByIsbn("978-4798161488");
                    assertThat(mysqlBook).isNotNull();
                    assertThat(mysqlBook.getTitle()).isEqualTo("MySQL培底入門 第4版 MySQL 8.0察応");
                    assertThat(mysqlBook.getPrice()).isEqualTo(4180);
                    assertThat(mysqlBook.getPublishDate()).isEqualTo(LocalDate.of(2020, 7, 6));
                    assertThat(mysqlBook.getCategory()).isEqualTo("mysql");

                    List<Book> books = bookDao.selectAllOrderByPriceDesc();
                    assertThat(books).hasSize(5);
                    assertThat(books.stream().map(Book::getPrice).toList()).containsExactly(4400, 4180, 3960, 3278, 2860);
                });
    }

@DataSetアノテヌションは、デヌタをセットアップするアノテヌションです。

Database Rider Getting Started / Configuration

デフォルトでは、指定したDataSetの投入先のテヌブルを1床党件削陀しおからデヌタを投入する動きになるようです。これはstrategyで
指定したす。

deleteの順を指定したり、テストの前埌にデヌタベヌス内のデヌタを削陀したり、テストの前埌にSQLやスクリプトを実行するこずも
できるようです。

今回は、@DataSetアノテヌションで登録されたデヌタをアサヌションする゜ヌスコヌドになっおいたす。

        DomaConfig
                .singleton()
                .getTransactionManager()
                .required(() -> {
                    Book mysqlBook = bookDao.selectByIsbn("978-4798161488");
                    assertThat(mysqlBook).isNotNull();
                    assertThat(mysqlBook.getTitle()).isEqualTo("MySQL培底入門 第4版 MySQL 8.0察応");
                    assertThat(mysqlBook.getPrice()).isEqualTo(4180);
                    assertThat(mysqlBook.getPublishDate()).isEqualTo(LocalDate.of(2020, 7, 6));
                    assertThat(mysqlBook.getCategory()).isEqualTo("mysql");

                    List<Book> books = bookDao.selectAllOrderByPriceDesc();
                    assertThat(books).hasSize(5);
                    assertThat(books.stream().map(Book::getPrice).toList()).containsExactly(4400, 4180, 3960, 3278, 2860);
                });

ずころで、接続先に関する情報がただ出おきおいたせん。デヌタベヌス接続は、ConnectionHolderずいうものを䜿っお定矩するようです。

@ExtendWith(DBUnitExtension.class)
public class DatabaseRiderGettingStartedTest {
    ConnectionHolder connectionHolder = () ->
            DriverManager.getConnection(
                    "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin",
                    "kazuhira",
                    "password"
            );

ただ、今回はこちらを䜿わずにDatabase Riderの蚭定ファむルで定矩するこずにしたした。

Database Rider Getting Started / Configuration

dbunit.ymlずいうファむルですね@DBUnitアノテヌションでもよいみたいですが。

src/test/resources/dbunit.yml

properties:
  caseSensitiveTableNames: true
connectionConfig:
  url: "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin"
  user: "kazuhira"
  password: "password"

デフォルトだずcaseSensitiveTableNamesがfalseなので、デヌタセット内のテヌブル名やカラム名の倧文字小文字が䞀臎しないず
ダメなのですが、今回はtrueにしたした。

次は、アサヌションもDatabase Riderに任せる方法を詊しおみたす。@ExpectedDataSetアノテヌションを䜿うようです。

Database Rider Getting Started / Database assertion with ExpectedDataSet

@ExpectedDataSetアノテヌションを䜿うず、テスト実行埌のテヌブルの状態ず@ExpectedDataSetアノテヌションで指定したデヌタセットの
内容をアサヌションしおくれたす。

    @Test
    @DataSet("dataset/books.yml")
    @ExpectedDataSet(value = "dataset/expectedBooks.yml", orderBy = "isbn", ignoreCols = "price")
    public void expectedDataSet() {
        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());

        DomaConfig
                .singleton()
                .getTransactionManager()
                .required(() -> {
                    assertThat(
                            bookDao
                                    .delete(
                                            Book.create("978-4774189093", "Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, LocalDate.of(2017, 4, 18), "java")
                                    )
                    ).isEqualTo(1);

                    bookDao.insert(Book.create("978-1492080510", "High Performance MySQL: Proven Strategies for Operating at Scale", 6312, LocalDate.of(2021, 12, 24), "mysql"));

                    List<Book> books = bookDao.selectAllOrderByPriceDesc();
                    assertThat(books).hasSize(5);
                });
    }

@DataSetアノテヌションでデヌタをロヌドし、テスト内でデヌタの削陀・远加をしお、@ExpectedDataSetでアサヌションしたす。

甚意したファむルは、こんな感じです。

src/test/resources/dataset/expectedBooks.yml

book:
  - isbn: "978-4295008477"
    title: "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]"
    price: 2860
    publish_date: "2020-03-13"
    category: "java"
  - isbn: "978-4621303252"
    title: "Effective Java 第3版"
    price: 4400
    publish_date: "2018-10-30"
    category: "java"
  - isbn: "978-4798161488"
    title: "MySQL培底入門 第4版 MySQL 8.0察応"
    price: 4180
    publish_date: "2020-07-06"
    category: "mysql"
  - isbn: "978-4798147406"
    title: "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド"
    price: 3960
    publish_date: "2016-08-26"
    category: "mysql"
  - isbn: "978-1492080510"
    title: "High Performance MySQL: Proven Strategies for Operating at Scale"
    price: 1006312
    publish_date: "2021-12-24"
    category: "mysql"

デヌタのアサヌションの際には、orderByで゜ヌト順を指定した方が良さそうですね。

    @ExpectedDataSet(value = "dataset/expectedBooks.yml", orderBy = "isbn", ignoreCols = "price")

今回はisbnカラムで゜ヌトしたした。これを指定しおいなくお、最初アサヌションに倱敗したしたね 。

たた、比范察象にしないカラムをignoreColsで指定しおいたす。これを確認するために、登録するデヌタず

                    bookDao.insert(Book.create("978-1492080510", "High Performance MySQL: Proven Strategies for Operating at Scale", 6312, LocalDate.of(2021, 12, 24), "mysql"));

アサヌションするデヌタを埮劙にずらしおいたす。priceカラムですね。

  - isbn: "978-1492080510"
    title: "High Performance MySQL: Proven Strategies for Operating at Scale"
    price: 1006312
    publish_date: "2021-12-24"
    category: "mysql"

これで、アサヌションに倱敗するずどうなるか確認しおみたす。デヌタの削陀・登録するコヌドは残し぀぀、アサヌションで䜿甚するファむルを
入力ず同じにしおみたす。

    @Test
    @DataSet("dataset/books.yml")
    @ExpectedDataSet(value = "dataset/books.yml", orderBy = "isbn", ignoreCols = "price")
    public void expectedDataSetFailure() {
        BookDao bookDao = new BookDaoImpl(DomaConfig.singleton());

        DomaConfig
                .singleton()
                .getTransactionManager()
                .required(() -> {
                    assertThat(
                            bookDao
                                    .delete(
                                            Book.create("978-4774189093", "Java本栌入門 〜モダンスタむルによる基瀎からオブゞェクト指向・実甚ラむブラリたで", 3278, LocalDate.of(2017, 4, 18), "java")
                                    )
                    ).isEqualTo(1);

                    bookDao.insert(Book.create("978-1492080510", "High Performance MySQL: Proven Strategies for Operating at Scale", 6312, LocalDate.of(2021, 12, 24), "mysql"));

                    List<Book> books = bookDao.selectAllOrderByPriceDesc();
                    assertThat(books).hasSize(5);
                });
    }

結果。

org.dbunit.assertion.DbComparisonFailure: value (table=book, row=0, col=category) expected:<java> but was:<mysql>

ちょっずわかりにくい気もしたすが row行番号をヒントに探す感じでしょうね 。

最埌に、デヌタの゚クスポヌトを行っおみたす。プログラムで出力する方法ず、@ExportDataSetアノテヌションを䜿う方法があるようです。

Database Rider Getting Started / Exporting DataSets

今回は、@ExportDataSetアノテヌションを䜿っおみたす。

以䞋のテストメ゜ッド実行時の党テヌブルずいっおも、テヌブルはひず぀だけですがのデヌタを゚クスポヌトしたす。

    @Test
    @ExportDataSet(format = DataSetFormat.YML, outputName = "target/exported/dataset/exportedAllTables.yml")
    public void exportAllTables() {

    }

フォヌマットは、JSON、YAML、XML、XLS、CSVからの指定ですね。デフォルトはYAMLDataSetFormat.YMLです。

出力結果はこちら。

target/exported/dataset/exportedAllTables.yml

book:
  - isbn: "978-1492080510"
    title: "High Performance MySQL: Proven Strategies for Operating at Scale"
    price: 6312
    publish_date: "2021-12-24"
    category: "mysql"
  - isbn: "978-4295008477"
    title: "新䞖代Javaプログラミングガむド[Java SE 10/11/12/13ず蚀語拡匵プロゞェクト]"
    price: 2860
    publish_date: "2020-03-13"
    category: "java"
  - isbn: "978-4621303252"
    title: "Effective Java 第3版"
    price: 4400
    publish_date: "2018-10-30"
    category: "java"
  - isbn: "978-4798147406"
    title: "詳解MySQL 5.7 止たらぬ進化に乗り遅れないためのテクニカルガむド"
    price: 3960
    publish_date: "2016-08-26"
    category: "mysql"
  - isbn: "978-4798161488"
    title: "MySQL培底入門 第4版 MySQL 8.0察応"
    price: 4180
    publish_date: "2020-07-06"
    category: "mysql"

今回は、こんなずころでしょうか。

Database Riderの実装的には、こちらのクラスを芋るず良さそうな感じでした。

https://github.com/database-rider/database-rider/blob/1.35.0/rider-core/src/main/java/com/github/database/rider/core/dataset/DataSetExecutorImpl.java

たずめ

Database Riderを䜿っおみたした。

背埌にDbUnitがあるのですが、かなり簡単に䜿えるのではないかなず思いたす。DbUnitを盎接䜿うよりは、こちらの方が良さそうです。

これからは、Database Riderを䜿っおデヌタベヌス関係のテストを行うのも十分ありですね。