TestContainersを触ってみる




TestContainersとSpring Data JDBCを触ってみて自分なりにまとめてみました。


# TestContainersとは

https://www.testcontainers.org/

TestContainersはデータベース、Selenium Webブラウザー、などのインスタンスを提供します。
TestContainersを使用すると次の種類のテストが簡単になるようです。

* データアクセスレイヤに対するインテグレーションテスト
  * MySQL、PostgreSQL、Oracleデータベースのコンテナ化されたインスタンスを使用
  * コンテナ化できる他のデータベースも使用可能
* アプリケーションインテグレーションテスト
  * データベース、メッセージキューなどに依存関係があるアプリケーションレイヤーのテストコードに対するテスト
* UI/受け入れテスト
  * Seleniumと互換性のあるコンテナ化されたWebブラウザを使用



# TestContainersを使うためには（事前準備）

### 環境
* Windows 10 Pro
* Java 11
* Apache Maven 3.8.2
* Docker 20.10.11

### TestContainersを依存関係に加える
TestContainersを使うための準備です。

Dockerの要件をみて、version等に問題ないか確認します。
https://www.testcontainers.org/supported_docker_environment/

windowsの場合はこちらも確認しといた方がよさそうです。
https://www.testcontainers.org/supported_docker_environment/windows/

データベースはPostgreSQLを使用してみます。
https://www.testcontainers.org/modules/databases/postgres/


テストフレームワークはJUnit5を使用することにします。

JUnit5を使用する場合には、`junit-jupiter`を追加する必要があります。

https://www.testcontainers.org/test_framework_integration/junit_5/#adding-testcontainers-junit-5-support-to-your-project-dependencies


なので、pomはこんな感じ。

```pom.xml

<dependency>
    <groupId>org.testcontainers</groupId>
		<artifactId>postgresql</artifactId>
		<version>1.16.2</version>
		<scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
		<artifactId>junit-jupiter</artifactId>
		<version>1.16.2</version>
		<scope>test</scope>
</dependency>

```

### SpringBootの依存関係

SpringBoot 2.6.1を使用します。

```pom.xml

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.6.1</version>
	<relativePath/> 
</parent>

```

データベースアクセスにはSpring Data JDBCを使います。
追加したライブラリはこんな感じ。

```pom.xml

  <dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jdbc</artifactId>
  </dependency>
  <dependency>
		<groupId>org.postgresql</groupId>
		<artifactId>postgresql</artifactId>
		<scope>runtime</scope>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
  <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
  </dependency>

```


# Spring Data JDBCを使用したデータベースアクセス

### 接続設定
application.propertiesにはPostgreSQLへ接続するための設定情報を記述していきます。

後述しますが、`spring.datasource.url`は記述していないです。
テストコード実行時に接続先のコンテナのURLを動的に割り当てます。



また今回はSpringBootのテーブルの初期化機能を使います。

schema.sqlやdata.sqlを用意し、DDLを記述します。
`spring.sql.init.mode=always`としています。

詳しくはこのあたり
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.data-initialization.using-basic-sql-scripts

```application.properties

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=username
spring.datasource.password=password
spring.sql.init.mode=always

```

次にDDLを記述します。(insert文も書いています。)

```schema.sql

create table stock
(
    id       integer,
    name     text,
    quantity integer,
    primary key (id)
);

insert into stock(id, name, quantity)
values (1, 'りんご', 5);



```

### Spring Data JDBC
リファレンスはこのあたり。

https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#reference


サンプルはこのあたり。
https://github.com/spring-projects/spring-data-examples


テーブルに対応するオブジェクトです。

lombokを使用しています。

主キーにはSpringのアノテーション`@Id`を付与しておきます。

https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/annotation/Id.html


```java

@AllArgsConstructor
@Data
public class Stock {
    @Id
    private Integer id;
    private String name;
    private Integer quantity;
}

```

上記のオブジェクトを操作するために、

[`CrudRepository`](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html)を継承した`Repository`インタフェースを作成します。


`CrudRepository`を継承するだけで簡易なCRUD機能が提供されるので今回は継承するだけでもいいですが、
テキトーな自分で書いたSQLのメソッドも書いておきます。

```java
public interface StockRepository extends CrudRepository<Stock, Integer> {

    @Query("update stock set quantity=:quantity where id=:id")
    Integer update(@Param("id") Integer id, @Param("quantity") Integer quantity);

}
```



# 動作確認

### 準備
それではテストコードを書いて動かしてみます。

まず以下のようなテストクラスを作ります。

```java

@Testcontainers
@SpringBootTest
public class PostgresqlContainerTest {
    @Container
    private static final PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>(DockerImageName.parse("postgres:12"))
                    .withUsername("username")
                    .withPassword("password")
                    .withDatabaseName("postgres")

    @Autowired
    StockRepository repository;

    @DynamicPropertySource
    static void setup(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
    }
}

```

https://javadoc.io/doc/org.testcontainers/junit-jupiter/latest/org/testcontainers/junit/jupiter/Testcontainers.html

をみると、JupiterもしくはJUnit5を使用する場合、テストクラスには`@Testcontainer`を付ける必要がありそうです。


`DockerImageName.parse()`の引数にはイメージ名を指定します。
イメージにはPostgreSQLのversion12を指定しました。

`@Container`をつけた変数には`static`で定義していますが、この場合にはテストメソッドが実行される前に開始されて最後のメソッドで停止されます。
その結果、テストメソッド間で共有されます。

staticフィールドでなければ、メソッド実行されるたびに開始されるようです。


`@DynamicPropertyRegistry`を使って、起動中のコンテナへ接続するための情報を設定します。

### 実行

それでは最後に実行してみます。

```java

    @Test
    void test() {
        Assertions.assertThat(postgres.isRunning()).isTrue();

        final Optional<Stock> stock = repository.findById(1);
        Assertions.assertThat(stock.get()).isEqualTo(new Stock(1, "りんご", 5));

        final Integer update = repository.update(1, 20);
        Assertions.assertThat(update).isEqualTo(1);

    }

```


# まとめ
TestContainersを使用してテストコードの記述までやってみました。

h2でも統合テストはできますが一部のSQLが実行できない、なんてこともあると思うので
それに比べると便利だと思います。

ただ、テストの実行がやや重い。。

おわり。