テストコードの書き方分からんくなったのでまとめてみる(概要,Repository)

# 背景
久方ぶりにJavaとSpringを触る機会がやってきて、基礎的な所を忘れているなぁとか分からんなぁと思いながら騙し騙しやっています。

保守のしやすさを保つためにテストコード書けるのは必須だよなぁと思って勉強してたのですが、
まぁ色んな書き方のパターンがあって覚えきれないなぁと思ったのでこの際少しずつ勉強していこうかと思います。

# 参考書籍
https://www.amazon.co.jp/dp/4297136139

- テスト構造
- ユニットテスト
- Repositoryのテスト
- (飽きちゃったので続きはまたそのうち別の記事でも)


# Springのテストサポート機能
Spring FrameworkやSpringBootではアプリケーションのテストをするときに役立つ機能(ユーティリティ,アノテーション)が用意されてます。
下記にあるように`spring-boot-starter-test`を導入すると様々なテスト系のライブラリを導入することができます。
https://spring.pleiades.io/spring-boot/docs/current/reference/html/features.html#features.testing.test-scope-dependencies

またテストには欠かせない様々な機能を提供してくれているのもポイント。
- DIコンテナを自動的に生成できる
- テストクラスに任意のBeanをインジェクションできる
- 自動的にロールバックできる

Springの肝としてDIコンテナがあると思っているのですが、テスト時にDIコンテナを作成するためにいくつかのアノテーションが用意されてます。
`@SpringBootTest` : 通常と同じようにDIコンテナを生成できる。E2Eテストやインテグレーションテストで使用する。
`@WebMvcTest` : Web周りのコンフィグレーションだけを行うアノテーション。Controllerのテストで使用する。
`@JdbcTest` : データベース周りだけのコンフィグレーションを行う。ほかにもMybatisとかDataJpa専用のアノテーションもある。
JUnitもこの中に含まれるため、特段気にすることなくJUnitのテストを書き始めることができます。


横断的にテストしたい場合はSpringBootTest、個別にテストしたい場合はそれに適したテストを選択することですみわけを行っているようです。
(Serviceのテストだけは特にコンフィグレーションつけないらしい)

# テストクラスの配置場所

一般的には以下の構成になります。src/main配下が実装コード、src/test配下がテストコード。
テスト対象のクラス(例だとFugaServiceクラス)をJavaConfigクラスより**下位のパッケージ**に配置してあげることでJavaConfigクラスを以下のような順序で探し出してくれます。
1. com.example.hogehoge.service配下にJavaConfigがあるかを探す
1. com.example.hogehoge配下にJavaConfigがあるかを探す → MainApplication.javaがあったのでそちらの設定を利用し、DIコンテナに読みこまれる

```
src/main/java
└── com.example.hogehoge
    ├── MainApplication.java ← JavaConfigクラス(@SpringBootApplicationがついているクラス)
    ├── controller
    ├── service
    │       └── FugaService.java ← テストしたいクラス
    └── ...

src/test/java
└── com.example.hogehoge
    ├── MainApplication.java
    ├── controller
    ├── service
    │       └── FugaServiceTest.java ← テストしたいクラス
    └── ...
```

# Repositoryのテスト

例えば以下のようなRecordクラスとRepositoryクラスがあるとします。

```src/main/java/com/example/demo/model/Staff.java
package com.example.demo.model;

public record Staff(Integer id, String name) {
}

```


```src/main/java/com/example/demo/repository/StaffRepository.java
package com.example.demo.repository;

import java.util.List;

import org.springframework.jdbc.core.DataClassRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import com.example.demo.model.Staff;

public class StaffRepository {
	final private JdbcTemplate jdbcTemplate;

	public StaffRepository(JdbcTemplate template) {
		this.jdbcTemplate = template;
	}

	public List<Staff> selectAll() {
		return this.jdbcTemplate.query(
				"select * from staff order by id",
				new DataClassRowMapper<>(Staff.class));
	}

	public Integer update(Staff staff) {
		return this.jdbcTemplate.update("update staff set name=? where id=?", staff.name(),staff.id());
	}
}

```

このRepositoryのselectAllとupdateのテストをしようとしたいとき、以下のようなテストコードが書けます。

```src/test/java/com/example/demo/repository/StaffRepositoryTest.java
package com.example.demo.repository;

import static org.junit.jupiter.api.Assertions.*;

import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.jdbc.Sql;

import com.example.demo.model.Staff;

@JdbcTest // ※1
@Sql("StaffRepositoryTest.sql") // ※2
public class StaffRepositoryTest {
	@Autowired
	JdbcTemplate jdbcTemplate;
	StaffRepository staffRepository;

	@BeforeEach
	void setup() {
		this.staffRepository = new StaffRepository(jdbcTemplate);
	}

	@Test
	public void test_selectAll() {
		List<Staff> resultList = this.staffRepository.selectAll();
		assertEquals(1, resultList.size());
		assertEquals(1, resultList.get(0).id());
		assertEquals("Taro", resultList.get(0).name());
	}

	@Test
	public void test_update() {
		Staff staff = new Staff(1, "Taro test");
		Integer result = this.staffRepository.update(staff);
		assertEquals(1, result);

		List<Staff> updatedStaffList = this.staffRepository.selectAll();
		Staff target = updatedStaffList.stream().filter(s -> s.id() == staff.id()).findFirst().orElse(null);
		assertEquals(staff.name(), target.name());
	}
}
```

### ※1 @JdbcTest
@jdbcTestは前述したとおり、データベース周りだけのコンフィグレーションを行います。
また各テストが終わったときに自動的にロールバックしてくれる機構があるため、
各テストに影響が出ずに実行することができます。

### ※2 @Sql
テスト実行時に取り込みたいSQLファイルを指定します。
これはクラスに対しても指定可能ですし、メソッドに対しても指定可能です。
```src/test/resources/com/example/demo/repository/StaffRepositoryTest.sql
INSERT INTO staff(id, name) VALUES(1, 'Taro');
```

一つ注意点があるとすれば、**指定するパスはテスト対象のプログラムから見たときの位置**だということです。

今回だとStaffRepositoryTest.javaがあるのは以下の通りです。
src/test/**java**/com/example/demo/repository/StaffRepositoryTest.java

@Sqlは実行されるプログラムと同じ階層構造の**resources配下**のファイルを参照しに行くような形となっているため、`StaffRepositoryTest.sql`と記載した場合は以下を見に行きます。
src/test/**resources**/com/example/demo/repository/StaffRepositoryTest.sql


# まとめ
ひとまずテストコードの概要とRepositoryの簡単なテストの書き方についてまとめてみました。
コード手を動かしながら書いてみていろいろと設定悩んだりする部分も多かったので、
まだまだ慣れないなぁと思ってます。
ServiceやControllerのテストなども書けるようになっていきたいのでまたそのうちまとめてみようと思います。