JUnit未経験者がテストコードを書ききるためにまずやったことと、初心者的悩みポイントを逆引き解説

# はじめに : ユニットテストを書いたことない頃の私の声
わかっているんです。知っているんです。ユニットテストを書いた方がいいのは。

だけどさ何から始めればいいの？
どうやってテストコード追加するの？
なんかアノテーションがやたらあるのは知ってる。ただ使い方がわからん。
ていうか時間がない！書く時間というか、そもそも最初のキャッチアップの時間が！作れない！

というのは数か月前までの私の心の声。

その後、ユニットテストを2つほど書いてみたところで、とりあえず上記のような心の声には「大丈夫だよ、これで始められるよ」と寄り添えるようになった気がします。
そこで

- ユニットテストナニモワカラナイ（本来の意味）・・から、どきどきしつつも挨拶ができるようになるくらいまでに何をしたか
- テストを書くにあたって最初に直面した「これってどうしたらいい・・・？」を逆引き解説※

をまとめます。

※解説とか大層なことを言っていますが、初心者です。間違っているところや「もっとこう書いたらいいよ！」ということがあると思うのでぜひご意見お願いします


# 環境
- Windows10
- Eclipse IDE 2021-09
    - [Plugin][Quick JUnit 0.7.0](http://quick-junit.osdn.jp/)
- Java16
- JUnit5
    - [JUnit5.8.1](https://junit.org/junit5/)
    - [mockito 1.10.19](https://site.mockito.org/)
    - [Hamcrest2.2](http://hamcrest.org/JavaHamcrest/)


# とにかくまずやったこと（所要時間合計：約4時間）
## 【まずやったこと①】準備運動：ライブコーディング動画を写経し、自分でユニットテストを書くための雰囲気をつかむ（3時間）

テストの書き方がとにかく全くわからないので、なにはともあれテストコードを書く流れを知ることが第一。そのためにはテストを書ける人がどう書いているのかを知るのが1番。
ということで私が最初に参考にしたのがこちら。

https://youtu.be/UhHdnLTxOjE

[FizzBuzz問題](https://ja.wikipedia.org/wiki/Fizz_Buzz#FizzBuzz%E5%95%8F%E9%A1%8C)とQ&Aのアーカイブ機能の開発というもう少し実践的な実装を、TDD（Test-Driven Development：テスト駆動開発）でライブコーディングしていく動画です。
コーディングだけでなく、テストコードを書く重要性、TDDのメリットや課題などの理論的な部分なども含めて説明してくれます。

こちらのライブコーディングの内容を[写経](https://runteq.jp/blog/programming-school/gakusyu/8321/)しつつ、ここで@Testや@ParameterizedTest、@CsvSourceなどのアノテーションの存在を知りました。
自分がテストを書きたいなと思っていたメソッドが、2つの設定値の組み合わせパターンで返り値が変わるというものだったので、この辺り使えそうだなーなどとあたりをつけるのにも参考になりました。

なお、私はすでに社内で用意されていたJUnit導入済みのプロジェクトを利用して始めてしまいましたが、あらかじめ[こちら](https://sukkiri.jp/technologies/devtools/junit/junit5-with-eclipse.html)などを参考にしてJUnit5を導入したうえで写経するのがおすすめです。

そもそも自動テストとは？TDDとは？といった説明部分は1.5倍速などで聴きつつ、コーディング部分は一時停止しつつでやって大体2時間半ほど。

https://youtu.be/Q-FJ3XmFlT8

[@t-wada](https://twitter.com/t_wada)さんによるライブコーディング。
先に1つめの動画を見たので2つめに関しては準備運動当初はつまみ食いしただけですが、テストを書き起こすためのパターンの整理方法やTDDのサイクルやスキルの話などとても参考になりました。[^キャッチアップ動画社内的には2つめ]

[^キャッチアップ動画社内的には2つめ]:実は社内で同僚がTDDのキャッチアップのために紹介していたのはこちらの動画でした・・・が、どうやら私は途中で関連リンクなどから1つ目の動画を開き、それを先に視聴していたようです。2つとも見てみた結果、どちらから見てもよかったなという印象です。


## 【まずやったこと②】動画内で存在を知ったアノテーションやテストメソッドについてかるーく確認（30分）
実際は[【まずやったこと①】](#まずやったこと準備運動ライブコーディング動画を写経し自分でユニットテストを書くための雰囲気をつかむ3時間)と並行でやりましたが、ライブコーディングで出てきたアノテーションやメソッドを下記のサイトで検索して調べました。。

https://oohira.github.io/junit5-doc-jp/user-guide/

**v5.3.0時点**の公式サイトの日本語訳なので最新の情報ではないですが、概要を掴むのには敷居が低くてよかったです。
[英語版の最新の公式サイト](https://junit.org/junit5/docs/current/user-guide/)と比較すると、アノテーション部分だけでも差分が出ているので、自プロダクトで利用しているJUnit5のバージョン次第では公式を見てください。


## 【まずやったこと③】テストクラスの作成（15分）
テスト対象のクラスを起点にテストクラスを作ります。

Eclipseのテストを書きたいと思ったクラスの上でショートカット`Ctrl + 9`を入力します。[^quickJunitがない場合]
このショートカットで、すでにテスト対象クラスに紐づくテストクラスが存在する場合は該当のテストクラスを、紐づくテストクラスがない場合はこのダイアログが開くので`Yes`を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/be9039ed-da5f-84aa-ca2b-26c882347a0f.png)
これでテストクラスの作成はほぼ完了です。

[^quickJunitがない場合]: Quick JUnitプラグインが入っていない場合は`Help > Eclipse Marketplace...`から検索し、インストールしてください。


### Tips: JUnit5でテストを書くときは "New JUnit Jupiter test"を選択
New JUnit Test Caseのダイアログが開いたのでJUnit5を選択しようと思ったけれど
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/461ede0c-3aac-50c3-cb54-e3a257ca84f0.png)
あれ、`New JUnit 5 test`の選択肢がない・・？

と思ったら、[こちらの記事][JUnit 5によるテストの基本]によると`JUnit Jupiter：JUnit 5 でテストや拡張機能を作成するためのモジュール`とのこと。なので3つ目の**New JUnit Jupiter test**を選択すればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/a78fc2ca-25f1-e02a-97ae-24a167d2d200.png)
自分で一からセットアップしていれば「そりゃそうだろ」という感じですが、はじめてテストコードを書いたのは社内の既存プロジェクト。すでにJUnit5がビルドパスに組み込まれていたため基本的なことを知りませんでした・・・


### Tips: テストクラス作成時のお作法
src/main/java/Hoge.javaというクラスに対してテストクラスを作る場合、

- packageはsrc/**test**/java
- 命名はHoge**Test**.java

とするのがお作法です。

また、テストクラスは、テスト対象クラスを継承するとテスト対象のメソッドを呼びやすいです（継承しなくてもOK）。

このあたりはEclipseがテスティングペア作成時によろしくやってくれるので、source folderやpackageなどをデフォルトのまま作成すれば問題ありません。


## 【まずやったこと④】まずはとにかくテストを書いてみる（15分）
まずやったことの[①](#まずやったこと準備運動ライブコーディング動画を写経し自分でユニットテストを書くための雰囲気をつかむ3時間)や[②](#まずやったこと動画内で存在を知ったアノテーションやテストメソッドについてかるーく確認30分)でちらほらテスト用のアノテーションやメソッドが登場するので、最初から複雑なことを考えてしまいがちです。
が、まずはとにかくシンプルに。以下だけでとりあえずテストを書いてみました。

- **@Test** : これはテストのためのメソッドですよ、と宣言するためのアノテーション。逆につけないとJUnitテストと認識されない
- **assertTrue(boolean condition)** : conditionがtrueを返すかを確認できる
- **assertFalse(boolean condition)** : conditionがfalseを返すかを確認できる

こんな感じで書いてみました。[^テストクラスとメソッドのアクセス修飾子]
```java:FunctionAuthManagerTest（テストクラス）
class FunctionAuthManagerTest extends FunctionAuthManager {
	@Test
	void withCommonConfigDependOnEmployee() {
		// テスト対象の`FunctionAuthManager#canManageEachConfigByEmployee`にEnumCommonConfig.DEPEND_ON_EMPLOYEEを渡したらtrueを返すことをテストする
		assertTrue(canManageEachConfigByEmployee(EnumCommonConfig.DEPEND_ON_EMPLOYEE, null));
	}
}
```
なお、テスト対象のサンプルコードとして`とあるサービスの利用権限を管理するためのクラス（FunctionAuthManager）`を用意しました。業務上のソースコードとしても割とよくある内容かなと思います。
```java:FunctionAuthManager（テスト対象クラス）
public class FunctionAuthManager {
	/**
	 * とあるサービスの利用設定をユーザー個人に管理させるか
	 */
	public boolean canManageEachConfigByEmployee(EnumCommonConfig config, CommonConfigProvider provider) {
		return switch (config) {
		case USE, UNUSE -> false;
		case DEPEND_ON_EMPLOYEE -> true;
		case UNDEFINED -> {
			EnumCommonConfig def = provider.getDefault();
			if (def == EnumCommonConfig.UNDEFINED) {
				System.err.println("Illegal default config value: " + def);
				yield false;
			}
			yield canManageEachConfigByEmployee(def, provider);
		}
		default -> {
				System.err.println("Unexpected value: " + config);
				yield false;
			}
		};
	}
}
```
```java:EnumCommonConfig（利用設定のパターンを集めたenum）
public enum EnumCommonConfig {
	USE, UNUSE, DEPEND_ON_EMPLOYEE, UNDEFINED;
}
```

[^テストクラスとメソッドのアクセス修飾子]:JUnit5ではテストクラスやテストメソッドをパッケージプライベートで書くことができます。JUnit4ではpublicである必要があったようです。

### 実際に手を動かしてみたい方へ
下記にサンプルソースを置いています。

https://github.com/moromi25/junit5-sample

#### 配置しているもの
- 空の状態の`FunctionAuthManagerTest`クラス
- `FunctionAuthManager`クラスをはじめとしたテスト対象のサンプルコード

この記事の中で各テストコードを同じように写経できるサンプルコードとなっています。
cloneし、`junit5-first-hands-on`ブランチをcheckoutしてご自身の環境にインポートして写経してみてください。


#### GitHubからのcloneおよびブランチのチェックアウト方法
Git Bashなどで
- git clone git@github.com:moromi25/junit5-sample.git 
- (junit5-sampleに移動)
- git checkout junit5-first-hands-on


#### Eclipseへのインポート方法
`Package Explorer上で右クリック > Import > General > Existing Projects into Workspace`


## 【まずやったこと⑤】テストを実行してみる（5分）
テストクラス上で`右クリック > Run As > JUnit Test`で実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/7da63311-d6ad-d6fc-d5e1-42d44ff3f8cd.png)
無事にテストが通りました！

ちなみに、敢えて`withCommonConfigDependOnEmployee`の中で`assertFalse`を使ってテストを失敗させてみると、こんな感じで教えてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/501370a2-6651-d880-87dd-5188e86477d8.png)


# 【まずやってみた所感】無事にテストは書けたけど・・・
よしテスト書けた！もう少し改良してみよう！と書き進めていく中で、こんな悩みや疑問が生まれました。

1. [正直assertTrue/Falseだけじゃ話にならないわ。返り値はbooleanばっかりじゃないし](#1-正直asserttruefalseだけじゃ話にならないわ返り値はbooleanばっかりじゃないし-assertthat-ほか)
1. [例外処理をテストしたい](#2-例外処理をテストしたい-assertdoesnotthrow-assertthrows)
1. [テスト実行結果、メソッド名だと正直内容わかりづらい](#3-テスト実行結果メソッド名だと正直内容わかりづらい-displayname)
1. [テスト対象コードで使うクラスがテストに都合のいい返り値などを返すようにしたい](#4-テスト対象コードで使うクラスがテストに都合のいい返り値などを返すようにしたい-mock)
1. [テスト対象クラスにメソッドがいくつもあって、各メソッドにつきテストを複数書いていって・・・あーもうどこに何のテストが書いてあるか迷子だわ](#5-テスト対象クラスにメソッドがいくつもあって各メソッドにつきテストを複数書いていってあーもうどこに何のテストが書いてあるか迷子だわ-nested)
1. [複数のテストで共通の事前処理を、全テスト実行前に1度だけしたい](#6-複数のテストで共通の事前処理を全テスト実行前に1度だけしたい-beforeall)
1. [あれ？privateメソッドのテストを書こうとすると怒られる・・・？](#7-あれprivateメソッドのテスト書こうとすると怒られる-visiblefortesting)
1. [テストメソッドを使いまわしたい](#8-テストメソッドを使いまわしたい-parameterizedtest)
1. [staticメソッドをMockしたい](#9-staticメソッドをmockしたいmockitomockstatic)

それぞれの悩み・疑問をもとに前述したテストコードを改良しつつ、その対処法について逆引き的に書いていきます。
これ以降は、必要なところをかいつまんで見てもらえればと思います。

あるいは、上記の疑問点を入れ込んだテストクラスのサンプルをGitHubに置いています。
ソースコードベースで見られれば十分です、という方は下記の`main`ブランチを見てみてください。

https://github.com/moromi25/junit5-sample


# 1. 正直assertTrue/Falseだけじゃ話にならないわ。返り値はbooleanばっかりじゃないし ⇒【assertThat ほか】

回答としては、JUnit5のAssertionの[Javadoc](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html
)やHamcrestというライブラリから提供されている[メソッドの解説](https://qiita.com/opengl-8080/items/e57dab6e1fa5940850a3)に目を通してみるのが1番といえばそれまでです。

ただ、何がテストしたいかって、**A(実測値)がB(予測値)の値と同じ/違うかを比較する**ことが非常に多いのではないでしょうか。

その場合は、Hamcrestの`assertThat`がおすすめです。[^hamcrestをすすめる理由]
[^hamcrestをすすめる理由]:業務上利用できるのがHamcrestだったため、AssertJなどは未検証です。比較表は[こちら](https://qiita.com/disc99/items/31fa7abb724f63602dc9)など参考になるかと。

最初に書いた`assertTrue(canManageEachConfigByEmployee(EnumCommonConfig.DEPEND_ON_EMPLOYEE, null));`というテストコードをassertThatを使って書き換えてみます。
```java
// 同値比較での置き換え
assertThat(canManageEachConfigByEmployee(EnumCommonConfig.DEPEND_ON_EMPLOYEE, null), is(true));

// 非同値比較での置き換え
assertThat(canManageEachConfigByEmployee(EnumCommonConfig.DEPEND_ON_EMPLOYEE, null), is(not(false))));

```
個人的おすすめポイントとしては、
- `assert that canManageEachConfigByEmployee is true`と英語ライクに読めることに好感
- 英語的に考えれば引数の順序が`1.実測値, 2.予測値`であると直感的にわかりやすい
- 第二引数に指定する[CoreMatcher](http://hamcrest.org/JavaHamcrest/javadoc/2.2/org/hamcrest/CoreMatchers.html)という判定用メソッドが豊富
    - もちろんboolean値以外の比較も書ける
    - not()を使えば「～でない」テストも可能
    - Matcherクラス自体をカスタマイズできるらしい
- 後述する[8. テストメソッドを使いまわしたい ⇒【@ParameterizedTest】](#8-テストメソッドを使いまわしたい-parameterizedtest)との親和性が高い
```java:boolean値以外の比較例
// int同士の比較
assertThat(Integer.MAX_VALUE, is(2147483647));

// Listに要素が含まれているか（比較ではないけどこんなメソッドも用意されている）
assertThat(Arrays.asList("hoge", "fuga"), hasItem("hoge"));
```

ちなみに、JUnitが提供している[assertEquals](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html#assertEquals(byte,byte))というメソッドもあります。
ただ、個人的には予測値と実測値がどちらだったか混乱します・・・
```java:assertEquals
// 第一引数が予測値、第二引数が実測値
assertEquals(true, canManageEachConfigByEmployee(EnumCommonConfig.DEPEND_ON_EMPLOYEE, null));
```

以上のことから、2つの値の比較で主に利用するのはHamcrestの`assertThat`がいいなと感じています。


# 2. 例外処理をテストしたい ⇒【assertDoesNotThrow, assertThrows】
## 例外が発生しないことをテストしたい ⇒【assertDoesNotThrow】
条件分岐の中で異常値が来た場合、エラーは吐きつつも処理は止めたくない、という実装は割とあると思います。
そんな「ちゃんと例外発生していないよね」を確認するのには`assertDoesNotThrow`メソッドを使います。
```java:assertDoesNotThrow
boolean canManageByEmployee = assertDoesNotThrow(() -> canManageEachConfigByEmployee(UNDEFINED));
assertThat(canManageByEmployee, is(false));

```

## 例外が発生することをテストしたい ⇒【assertThrows】
逆に、例外が発生することをテストする場合は`assertThrows`を使います。
```java:assertThrows
assertThrows(IllegalArgumentException.class, () -> canManageEachConfigByEmployee(UNDEFINED));
```


# 3. テスト実行結果、メソッド名だと正直内容わかりづらい ⇒【@DisplayName】
前掲の実行結果、ちゃんと読めばクラスのどのメソッドのテストなのかわかりますし、そのメソッドをたどればどんなテストか理解はできますが、結構面倒ですよね。
たとえば
```java
	void withCommonConfigDependOnEmployee() {
		// ★テスト対象の`FunctionAuthManager#canManageEachConfigByEmployee`にEnumCommonConfig.DEPEND_ON_EMPLOYEEを渡したらtrueを返すことをテストする
		...
```
この★をつけたコメントが実行結果に表示されたら、どんなテストをしているのか一目でわかるのになと。

そんな時に使うのが[@DisplayName](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/DisplayName.html)アノテーションです。文字通りそのテストの表示名称となります。
メソッドだけでなくクラスにもつけられます。
```java:FunctionAuthManagerTest（テストクラス）
@DisplayName("設定権限に関するテスト")
class FunctionAuthManagerTest extends FunctionAuthManager {
	@Test
	@DisplayName("DEPEND_ON_EMPLOYEEを渡したらtrueを返すか")
	void withCommonConfigDependOnEmployee() {
		...
```
実行結果がこちら。ただのクラス名、メソッド名の羅列よりも見やすくなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/9bbff044-b883-756d-3970-23b19bf78904.png)


# 4. テスト対象コードで使うクラスがテストに都合のいい返り値などを返すようにしたい ⇒【mock】
たとえば`ユーザーによる設定がされていない場合、システムデフォルト値を取得して判定する`処理、よくあると思います。こんな感じです。
```java:FunctionAuthManager
	boolean canManageEachConfigByEmployee(EnumCommonConfig config, CommonConfigProvider provider) {
		return switch (config) {
		...
		case UNDEFINED -> {
			// DBからデフォルト値を取得し、デフォルト値ベースで再判定
			EnumCommonConfig def = EnumCommonConfig.find(provider.getDefault());
			yield canManageEachConfigByEmployee(def, provider);
		}
		};
	}
```
```java:CommonConfigProvider
	private DefaultCommonConfigRepository repo;
	...
	/** DBからデフォルト値を取得 */
	public int getDefaultVal() {
		return repo.getDefaultVal();
	}
```
今回扱う`CommonConfigProvider#getDefault`はDBアクセスを伴う処理の想定です・・・ん？テストでDB処理・・・？！JUnit上でどうやればいいの？

そんな時に有効な手段の1つが、Mockitoが提供するstaticメソッド、**mock**です。

もっく とは・・・？と最初は思うかもしれませんが、mockという英単語には「疑似、まねる」という意味合いがあります。
なので、DBアクセスしてデータを取得する**本来の処理に似せたふるまいを、テストクラスの中で定義してあげる**、といったイメージです。

## 実際どう書くの？
こう書きます（一例）。
- モック化したいクラスのインスタンス化 : `mock(モック化したいクラス);`
- あるメソッドの返り値を指定 : `when(ふるまいを指定したいメソッド).thenReturn(指定の返り値);`
```java:FunctionAuthManagerTest
class FunctionAuthManagerTest extends FunctionAuthManager {
	/** デフォルト設定を返してくれるクラス */
	private CommonConfigProvider mockedDefaultConfigProvider;

	@Test
	@DisplayName("UNDEFINEDを渡したらデフォルト設定：UNUSEを使って再判定するか")
	void withCommonConfigUndefined_defaultUnuse() {
		// モック化したいクラスのインスタンス化
		mockedDefaultConfigProvider = mock(CommonConfigProvider.class);
		// モック化したクラス内のメソッドのふるまいを定義
		when(mockedDefaultConfigProvider.getDefault()).thenReturn(UNUSE);

		assertThat(canManageEachConfigByEmployee(UNDEFINED, mockedDefaultConfigProvider), is(true));
	}
}
```
これでCommonConfigProviderを**モック化したmockedDefaultConfigProvider#getDefaultメソッドは実際の実装がどうなっていようと必ずUNUSEを返す**ようになります。

注意点としては、テスト中にモック化したクラスのメソッドがよろしく動作してくれるのは、あくまでもふるまいを定義したメソッドのみということです。
そのため、**利用したいメソッドに関してはすべてふるまいを定義する必要**があります。定義していない場合、テスト実行時に`java.lang.NoSuchMethodError`が発生します。

そのほかふるまいの定義方法については[こちら](https://qiita.com/yakumo3390/items/c34f1f1625c3beffaf9c#-1---%E3%83%A2%E3%83%83%E3%82%AF%E3%81%AB%E3%83%86%E3%82%B9%E3%83%88%E7%94%A8%E6%88%BB%E3%82%8A%E5%80%A4%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B%E5%BC%95%E6%95%B0%E3%81%AA%E3%81%97-whenthenreturn-doreturnwhen)などを参考にしてみてください。


## Tips: mockとspy
モック化したクラスで利用したいメソッドすべてのふるまいを定義する必要がある、と記載しましたが、クラス内の一部だけモック化し、それ以外のメソッドは元のロジック通りの動きをさせたいということもあるかもしれません。

そんなときはmockではなく`spy`というメソッドを使うのがよさそうです。

[こちらの記事][mockとspyの違いとサンプルコード]によると、

> mock() はインスタンスの非 static 且つ public のメソッドをすべて Mock 化します。
> なので一部のメソッドを実装のまま使いたい場合には適しません。
> spy() は明示的に指定したメソッドのみを Mock 化します。
> Mock 化しないメソッドは実装通りのふるまいとなります。

とのことです。

## Tips: mock化の方法
モックの初期化方法はmockメソッドを利用した方法のほかに、[アノテーションを利用した方法][@Mockでモックを初期化する方法は3つある。]があります。


## 詰まった箇所: mockを利用したテスト実行時にjava.lang.ExceptionInInitializerError
Java9以上を利用する場合に発生する可能性があります。
もしかしたらJava9以上を使っている方には釈迦に説法かもしれませんが、普段Java8を使っているので解決に時間がかかりました・・・

<details><summary>エラーログ抜粋</summary>

```rb
java.lang.ExceptionInInitializerError
	at org.mockito.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:167)
	at org.mockito.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
	at org.mockito.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:217)
(省略)
	at test.main.FunctionAuthManagerTest$CanManageEachConfigByEmployeeTest.testWithCommonConfigUndefined(FunctionAuthManagerTest.java:61)
(省略)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @1ed1993a
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
	at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
	at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
	at org.mockito.cglib.core.ReflectUtils$2.run(ReflectUtils.java:57)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
	at org.mockito.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:47)
	... 147 more
```
</details>

### 解決方法（一例）
実行時にvm引数に`--add-opens java.base/java.lang=ALL-UNNAMED`を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/a7b8a8b2-97c2-749b-ba43-f32941b7b334.png)

### 原因
Java9 で導入された JavaPlatform Module System によりリフレクションを利用するフレームワークでInaccessibleObjectException が発生する場合があるらしいです。

今回はモジュールシステムは利用していなかったので、実行時のvm引数に--add-opens構文で指定することで解消しました。
モジュールシステムを利用している場合はmodule-info.javaへの対応が必要です。


# 5. テスト対象クラスにメソッドがいくつもあって、各メソッドにつきテストを複数書いていって・・・あーもうどこに何のテストが書いてあるか迷子だわ ⇒【@Nested】
テストクラスの中で子クラスを作ってグルーピングすることで迷子防止ができます。
その際、子クラスであることを定義するためにつけるアノテーションが@Nestedです。

今回は共通設定をコントロールする`canManageEachConfigByEmployee`というメソッドに関するテストと、実際にサービスへのアクセス権限をコントロールする`canUseService`というメソッドに関するテストの大きく2つに分かれるので、それぞれに関連するテストを1つのクラスにまとめて@Nestedを付けました。
```java
@DisplayName("設定権限に関するテスト")
class FunctionAuthManagerTest extends FunctionAuthManager {
	@Nested
	@DisplayName("共通設定に関するテスト")
	class CanManageEachConfigByEmployeeTest {
		@Test
		@DisplayName("DEPEND_ON_EMPLOYEEを渡したらtrueを返すか")
		void withCommonConfigDependOnEmployee() {
			...
		}
	}

	@Nested
	@DisplayName("共通設定×ユーザー設定を加味したアクセス可否テスト")
	class CanUseServiceTest {
		@Test
		@DisplayName("共通設定がUSEの場合trueを返すか")
		void withComAuthTypeUse() {
			...
		}
	}
}
```
実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/bb83301a-624c-4db2-6f0b-dd15fd60d26f.png)
ソース、テスト実行結果いずれも@Nestedでまとめられたクラスごとにグルーピングされ、可読性がぐっと上がりました。


# 6. 複数のテストで共通の事前処理を、全テスト実行前に1度だけしたい ⇒【@BeforeAll】
@BeforeAllアノテーションをメソッドにつけると、**同一クラス内にあるテストクラス実行の最初に実行される事前処理を書いたメソッドですよ**、と宣言できます。
今回の場合、1度だけ初期化し、その後は複数のテストメソッドで使いまわしたい`mockedDefaultConfigProvider`の初期化に使えます。
```java
@BeforeAll
setup() {
	// モック化したいクラスのインスタンス化
	mockedDefaultConfigProvider = mock(CommonConfigProvider.class);
}
```


テストの実施前後に処理を記載するために付与できるアノテーションは、このほかに以下が用意されています。
- @BeforeEach：クラス内の各テストメソッド実行**前に都度実行される事前処理**
- @AfterAll：同一クラス内にあるテストクラス実行後、**最後に実行される処理**
- @AfterEach：クラス内の各テストメソッド実行**後に都度実行される事後処理**


# 7. あれ？privateメソッドのテスト書こうとすると怒られる・・・？ ⇒【@VisibleForTesting】

privateメソッドのテストを書こうとテストクラスで呼び出したけどコンパイルエラーで怒られる・・・当然です。privateメソッドですから。テストクラスはテスト対象クラスを継承しているだけに過ぎません。テストクラスだからセーフとかはありません。

とはいえprivateメソッドにもテストを書きたい。そんなときは、privateメソッドを[アクセス修飾子](https://java-code.jp/134)なしのパッケージプライベートメソッドに変えます。

それだけだとただ可視性が上がっただけになってしまうので、Guavaの[@VisibleForTesting](https://www.javadoc.io/doc/com.google.guava/guava/12.0.1/com/google/common/annotations/VisibleForTesting.html)アノテーションをつけることで、後続の開発者に`このメソッドはprivateだったんだけどユニットテストのために可視性を上げてますよ`と伝えるためのマーキングになります。[^VisibleForTestingは可視性をあげるだけでもないらしい]

このアノテーションはテストコード側ではなく、**テスト対象のメソッドにつける**ものなので注意してください。

[^VisibleForTestingは可視性をあげるだけでもないらしい]:[こちらの記事][Guavaの@VisibleForTestingとは何か]によると、[SonarQube](https://www.sonarqube.org/)などの静的解析ツールで、誤って(テストコード以外の)他のクラスのコードからアクセスされていないかをチェックできるようです。

# 8. テストメソッドを使いまわしたい ⇒【@ParameterizedTest】
この記事で最初に書いたテストですが、テストしたいメソッドの引数になり得るenumのパターンは4種類あるので、全パターン書こうとするとこうなります。
```java
class FunctionAuthManagerTest extends FunctionAuthManager {
	@Nested
	@DisplayName("共通設定に関するテスト")
	class CanManageEachConfigByEmployeeTest {

		@Test
		void withCommonConfigDependOnEmployee() {
			assertTrue(canManageEachConfigByEmployee(EnumCommonConfig.DEPEND_ON_EMPLOYEE, null));
		}

		@Test
		void withCommonConfigUse() {
			assertFalse(canManageEachConfigByEmployee(EnumCommonConfig.USE, null));
		}

		@Test
		void withCommonConfigUnuse() {
			assertFalse(canManageEachConfigByEmployee(EnumCommonConfig.UNUSE, null));
		}

		@Test
		void withCommonConfigUndefined() {
			mockedDefaultConfigProvider = mock(CommonConfigProvider.class);
			// TODO パターン化
			when(mockedDefaultConfigProvider.getDefault()).thenReturn(UNUSE);
			assertThat(canManageEachConfigByEmployee(UNDEFINED, mockedDefaultConfigProvider), is(true));
		}
	}
}
```
最後の`withCommonConfigUndefined`は少し処理が複雑ですが、それ以外の3メソッドはenumとアサーションの真偽値の組み合わせの差はあれど、書かれているコードはほぼ同じなので何とかしてまとめたくなります。

そこで利用できるのが@ParameterizedTestです。@Testが1つのテストであることをマーキングするアノテーションであるのに対し、@ParametirizedTestはパラメータを持つことで**1つのテストメソッドに対して複数のテストパターンを作って実行**することができます。

@ParameterizedTestへパラメータを渡す方法はいくつかあり、いずれもアノテーションとその引数で指定します。

## @CsvSource
テストメソッドに対して複数の引数を渡したい時に使えます。
今回の場合、enumと実行結果の真偽値という2つのパラメーターをメソッドに渡してテストしたいので、こんな感じで書きます。

```java:@CsvSourceの利用方法
@ParameterizedTest
@DisplayName("EnumCommonConfigのパターンテスト(UNDEFINED以外)")
@CsvSource({
	"USE,                false", 
	"UNUSE,              false", 
	"DEPEND_ON_EMPLOYEE, true" 
})
void withCommonConfig(EnumCommonConfig config, boolean expected) {
	assertThat(canManageEachConfigByEmployee(config, null), is(expected));
}
```
3つのテストメソッドが1つのメソッドにすっきりとまとめられました！

メソッドの引数にenumを使用していると、暗黙の型変換を行ってくれます。賢いですね。
enumだけでなく、当然Stringやintといった値もCsvSourceに含むことができます。
enum以外の型を利用した@CsvSourceのサンプルは[こちら](https://junit.org/junit5/docs/5.8.0/user-guide/#writing-tests-parameterized-tests-sources-CsvSource)を見てみてください。


## @EnumSource
たとえばUSEとUNUSEのパターンだけでいえば期待結果も同じなため、パラメータ化したいのはEnumCommonConfigだけ、という場合は@EnumSourceが使えます。
```java:@EnumSourceの利用方法
@ParameterizedTest
@DisplayName("EnumCommonConfigのUSE, UNUSEパターンテスト")
@EnumSource(value = EnumCommonConfig.class, names = { "USE", "UNUSE" })
void withCommonConfig(EnumCommonConfig config) {
	assertThat(canManageEachConfigByEmployee(config, null), is(false));
}
```
上記の例では、`EnumCommonConfigの列挙子のうち、USEとUNUSEだけをテストするよ`と指定しています。

たとえば`EnumCommonConfigの全列挙子でテストを回したい`という場合は、これだけでOKです。
```java
@ParameterizedTest
@EnumSource
void withCommonConfig(EnumCommonConfig config) {
	...
```


## @ValueSource
今回は使いませんでしたが、さらに単純なintやString,Classなど1つの型のみでパラメータ化するのであれば、[@ValueSource](https://junit.org/junit5/docs/5.8.0/user-guide/#writing-tests-parameterized-tests-sources-ValueSource)を使うとよさそうです。


## @MethodSource
ここまで紹介したやり方はパラメータがすべて静的な場合でしたが、何かしら処理の実行をした上でパラメータ化したい場合には@MethodSourceが使えます。

mock化したクラスを利用したUNDEFINEDパターンのテストも含めてすべて1つのテストに含めようとすると、どうしてもデフォルト値の取得クラスをmock化する箇所が複雑になってしまうため、こんな感じで@MethodSourceを使ってみました。
```java:@MethodSourceの利用方法
	@ParameterizedTest
	@DisplayName("共通設定に関するテスト")
	@MethodSource({ "exceptUndefinedProvider", "undefinedProvider" })
	void withCanManageEachConfigByEmployee(EnumCommonConfig config, CommonConfigProvider provider, boolean expected) {
		assertThat(canManageEachConfigByEmployee(config, provider), is(expected));
	}

	static Stream<Arguments> exceptUndefinedProvider() {
		return Stream.of(
				arguments(USE, null, false),
				arguments(UNUSE, null, false),
				arguments(DEPEND_ON_EMPLOYEE, null, true)
		);
	}

	static Stream<Arguments> undefinedProvider() {
		return Stream.of(
				arguments(UNDEFINED, getMockedProvider(USE), false),
				arguments(UNDEFINED, getMockedProvider(UNUSE), false),
				arguments(UNDEFINED, getMockedProvider(DEPEND_ON_EMPLOYEE), true),
				arguments(UNDEFINED, getMockedProvider(UNDEFINED), false)
		);
	}
	
	static CommonConfigProvider getMockedProvider(EnumCommonConfig def) {
		CommonConfigProvider mockedDefaultConfigProvider = mock(CommonConfigProvider.class);
		when(mockedDefaultConfigProvider.getDefault()).thenReturn(def.getVal());
		return mockedDefaultConfigProvider;
	}
```

解説としては[こちらの記事](https://blog1.mammb.com/entry/2019/11/16/090000#MethodSource-%E3%81%A7%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF%E3%81%AB%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B)がわかりやすいです。
> Stream<Arguments> を返す static メソッドを用意し、@MethodSource("methodSourceProvider") として指定することで、メソッドで生成した値を引数に与えることがでます。

補足として、Argumentsインスタンスを作るのには`org.junit.jupiter.params.provider.Arguments.arguments`というファクトリーメソッドが用意されているので、ここにパラメータとして渡したい要素を入れ込みます。
今回は3つのパラメータをテストメソッドに渡す必要があるのでそれらを列挙しています。

今回@MethodSourceに複数のファクトリーメソッドを指定していますが、当然1つだけ指定してもOKです。1つの場合は`@MethodSource("exceptUndefinedProvider")`という感じで書けます。

引数にファクトリーメソッド名がない場合は、テストメソッド（今回だと`withCanManageEachConfigByEmployee`）というファクトリーメソッドがあるか探してくれるようです。

### @MethodSourceで使用するファクトリーメソッドの命名規則
これという指定はなさそうですが、[公式](https://junit.org/junit5/docs/5.8.0/user-guide/#writing-tests-parameterized-tests-sources-MethodSource)に倣って`hogeProvider`とするのがよさそうです。


### @Nested内で@MethodSourceを使ってエラーが発生したら ⇒【@TestInstance(TestInstance.Lifecycle.PER_CLASS)】
表題のとおり、@Nestedクラス内で@MethodSourceを使うと以下のエラーで怒られます。

> org.junit.platform.commons.PreconditionViolationException: Cannot invoke non-static method

その際、@Nestedをつけたクラスに **@TestInstance(TestInstance.Lifecycle.PER_CLASS)** アノテーションをつけることで解決します。


## Tips: @Testと@ParameterizedTestの実行結果の見え方
@Testメソッドを複数書いたとき、メソッド間の関連性は当然わかりません。そのため、関連性がわかるようにするためには@Nestedクラスなどで別途グルーピングする必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/28afb332-6cf8-fa4e-1d69-d3654463cc9c.png)

@ParameterizedTestでは、パラメータ化したパターンが1行ずつ結果に表示されるので、@Nestedクラスでグルーピングしなくても関連性がわかりやすいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/a521dae2-1427-f144-961f-3ca9df261f40.png)


## Tips: テスト対象メソッドで直接利用するわけではないが引数として渡した方がいい値 ⇒【期待結果、 パターン説明】
### テストの期待結果（expected）
ここまでの@ParameterizedTestの例で、テストメソッドの引数に`boolean expected`を渡しているものがいくつかあります（期待結果がbooleanなのは今回のサンプルの都合なので、当然intでもなんでもOKです）。

引数のパターンに応じて期待結果が変わるのは普通のことなので、テスト対象のメソッドで利用する値を渡すのと同様に、期待結果を渡すことで`assertThat`の第二引数を可変にできます。
[assertThat](#1.-正直assertTrue/Falseだけじゃ話にならないわ。返り値はbooleanばっかりじゃないし-⇒【assertThat-ほか】)の節で@ParameterizedTestとの親和性が高い、と書いたのはここが理由です。


### テストパターンの説明（description）
`String description`を渡すのもテスト実行結果の可読性を上げるのに有効です。
ソース内のdocとして有効なのは言うまでもありませんが、なぜコメントではなく引数として渡すのか。
expectedについてはアサーションで利用するために渡していますが、descriptionはテストメソッド内のどこでも利用しません。

では引数として渡す理由は何か。それは、テスト実行結果で利用するためです。
@ParameterizedTestの引数は実行結果に表示されるようになっているため、**第一引数にdescriptionを入れておくことで@DisplayNameの代用として利用**できます。
```java
	@ParameterizedTest
	@DisplayName("共通設定に関するテスト")
	@MethodSource({ "exceptUndefinedProvider", "undefinedProvider" })
	void withCanManageEachConfigByEmployee(String description, EnumCommonConfig config, CommonConfigProvider provider, boolean expected) {
		assertThat(canManageEachConfigByEmployee(config, provider), is(expected));
	}

	static Stream<Arguments> exceptUndefinedProvider() {
		return Stream.of(
				arguments("USE：ユーザー個別設定【不可】か", USE, null, false),
				arguments("UNUSE：ユーザー個別設定【不可】か",UNUSE, null, false),
				arguments("DEPEND_ON_EMPLOYEE：ユーザー個別設定【可能】か",DEPEND_ON_EMPLOYEE, null, true)
		);
	}

	static Stream<Arguments> undefinedProvider() {
		return Stream.of(
				arguments("UNDEFINED×デフォルトUSE：ユーザー個別設定【不可】か",UNDEFINED, getMockedProvider(USE), false),
				arguments("UNDEFINED×デフォルトUNUSE：ユーザー個別設定【不可】か",UNDEFINED, getMockedProvider(UNUSE), false),
				arguments("UNDEFINED×デフォルトDEPEND_ON_EMPLOYEE：ユーザー個別設定【可能】か",UNDEFINED, getMockedProvider(DEPEND_ON_EMPLOYEE), true),
				arguments("UNDEFINED×デフォルト未設定：ユーザー個別設定【不可】か（Exceptionが吐かれないかも確認）",UNDEFINED, getMockedProvider(UNDEFINED), false) // 異常系
		);
	}
```
実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/cb8968af-6f6e-51ce-bc33-347632c904df.png)

## 9. staticメソッドをMockしたい⇒【Mockito.mockStatic】

Mockito.mockStaticというメソッドを使ってstaticメソッドを持つクラスをMockすることができます。
注意点は必ずMockしたクラスを必ず解放することです。

書き方や注意点はどは下記を参照してください。

https://qiita.com/moromi25/items/61a8ae8750bec3078c10


# その他
### Tips: 実行後のカバレッジのハイライトを消したい
テスト実行すると、ソースのカバレッジ状況が緑／黄／赤でハイライトされます。テストのカバー率がわかるのは便利ですが、コーディングするときに邪魔になる・・・そんなときは、以下の方法でクリアできます。
`Window > Show View > Coverage`
タブ右側にある`Remove All Sessions`アイコンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366737/c57e621e-bf91-8ec0-304a-456013edf72e.png)


# おわりに：なぜユニットテストを書くべきか、現時点での個人的所感
特に業務上ユニットテストを書くメリットとしては以下が挙げられるかなと。

- 後からソースコードを見た人がどんなパターンを想定して実装しているかの判断材料になる
- 文字ベースで書いていた単体テストをコードに置き換えできる
    - たとえばExcelにパターンをしたためるより、そのパターンをコーディングの方が圧倒的に楽しくないですか？
- 単純なパターンテストをシステムにお願いできる
    - 設定画面でこのパラメータ変更して、実行画面で実行して・・の手間が大幅削減
- テスト再利用可能
    - 1度作れば何度でも、即再実行できる(もちろん機能追加などによるメンテナンスは必要ですが)
    - ビルドジョブに組み込むことでビルド時に一緒に実行できる
- リファクタリングするときの安心材料になる

最初のキャッチアップにこそ数時間かかりましたが、書き始めるとソースコードを書くのと同様で楽しいです。
またビルドジョブでユニットテストが失敗して余計な実装を入れていたことに気づくなど、`テストを書くことによる心理的安全性`を肌で感じました・・・

ソースコードを書くのと同じでテストコードを書くのも経験を積み重ねることでブラッシュアップされると感じるので、テストコードを1度書いて終わりではなく、定期的に振り返ってリファクタリングして、というのをやっていきたいです。

# 参考URL
- [【Java】JUnit 5によるテストの基本][JUnit 5によるテストの基本]

- [Amending Method and Class Visibility for JUnit 5](https://www.securecodewarrior.com/blog/amending-method-and-class-visibility-for-junit-5)
- [Why JUnit 5 default access modifier changed to package-private](https://stackoverflow.com/questions/55215949/why-junit-5-default-access-modifier-changed-to-package-private)

- [JUnit5 Assertions](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html)
- [Hamcrest](http://hamcrest.org/JavaHamcrest/javadoc/2.2/)
- [JunitでHamcrestを使用する理由](https://codechacha.com/ja/how-to-use-hamcrest-in-junit/#hamcrest%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E7%90%86%E7%94%B1)

- [Junitでモックを使ってみよう！](https://rightcode.co.jp/blog/information-technology/junit-mock-syain)
- [Mockitoを使ってみる。](https://qiita.com/yakumo3390/items/c34f1f1625c3beffaf9c)
- [Mockito mock() と spy() の違いとサンプルコード][mockとspyの違いとサンプルコード]
- [@Mockでモックを初期化する方法は3つある。][@Mockでモックを初期化する方法は3つある。]
- [InaccessibleObjectException の発生原因とその対処方](https://blog1.mammb.com/entry/2020/12/28/090000)

- [Guavaの@VisibleForTestingとは何か][Guavaの@VisibleForTestingとは何か]

- [[JUnit 5]@MethodSourceに非staticメソッドを指定するには](https://blog.ynkb.xyz/101/)
- [テストインスタンスのライフサイクル](https://www.ne.jp/asahi/hishidama/home/tech/java/junit/5/class.html#h_TestInstance.Lifecycle)

- [Eclipseでコードカバレッジのハイライトを削除する方法](https://dukesoftware00.blogspot.com/2020/05/eclipse.html)
- [InaccessibleObjectException の発生原因とその対処方](https://blog1.mammb.com/entry/2020/12/28/090000
)

[JUnit 5によるテストの基本]:https://b1san-blog.com/post/java/java-junit/
[mockとspyの違いとサンプルコード]:https://www.shookuro.com/entry/mockito-spy
[@Mockでモックを初期化する方法は3つある。]:https://ytnk531.hatenablog.com/entry/2018/08/11/120715
[Guavaの@VisibleForTestingとは何か]:https://qiita.com/flyaway/items/90c931b21dc5dc310d9e