JUnit5でのパラメータ化テスト

# パラメータ化テストとは
ひとつのテストケースに対し、複数パターンのパラメータをひとまとまりにして一気にテストすることをいう。
どのパラメータでテストが失敗したのかを確認することができる。

## 実装

日付の年月日を文字列で受け取りDate型に変換する。
その日付がカレンダー上に存在する有効な日付であれば、trueを
カレンダー上に存在しない無効な日付であれば、例外（ParseException）を
空文字の場合は、falseを返却するメソッドを実装した。

```java:DateCheck.java
package calc;

import java.text.DateFormat;
import java.text.ParseException;

/**
 * 日付チェッククラス
 */
public class DateCheck {

	/**
	 * 入力値の日付がカレンダーに存在する日付かを判定
	 * yyyy-MM-ddの場合、yyyy/MM/ddに変換する
	 *
	 * @param dateStr yyyy/MM/dd
	 * @return boolean
	 * @throws ParseException
	 */
	public boolean checkDate(String dateStr) throws ParseException {

		// 空チェック、桁数チェック
		if("".equals(dateStr) || dateStr == null || dateStr.length() < 8) {
			return false;
		}

		dateStr = dateStr.replace("-", "/");
		DateFormat date = DateFormat.getDateInstance();
	    // 日付/時刻解析を厳密に行うかどうかを設定
	    date.setLenient(false);

	    try {
	    	// 日付文字列をDate型に変換
			date.parse(dateStr);
			return true;
		} catch (ParseException e) {
			// 変換処理失敗時の処理
			throw new ParseException("有効な日付ではありません", 0);
		}
	}

}

```

DateFormatクラスのsetLenient()をfalseにすることで、年月日の解析を厳密に行うよう設定。
デフォルトだとtrueで設定されており、以下のように機転を利かせてくれるが今回はそれを無効化する。
　例：2020/01/32 →　2020/02/01


## テストケースを考える

- 正常系
1年が365日あり、それらを全てテストするのは工数的に現実的ではないだろう。
よって同値分割と限界値の観点から、月の初日と最終日に絞って正常系（有効な日付）をテストする。
月によって最終日が異なるため、月ごとに最終をパラメータとして設定する。

- 異常系
月の最終日を1日超えるようパラメータを設定する。
あと、yyyy/MM/ddとyyyy-MM-dd以外のフォーマットでパラメータを設定。
空文字やnullが設定されたときのテストケースも作成する。

## パラメータ化テストを書く

- 正常系
JUnit4までではパラメータをひとまとまりにするのに、@DataPointsや@DataPointでパラメータを設定し、
テストメソッドに@Theoryを設定していた。
JUnit5からはテストメソッドに、@ParameterizedTestを設定し、@ValueSourceでパラメータを設定する。

```java:DateCheckTest.java
/**
 * 日付チェックテストクラス
 */
class DateCheckTest {

	DateCheck dateCheck;

	@BeforeEach
	void setUp() {
		dateCheck = new DateCheck();
	}

	/**
	 * 正常系　yyyy/MM/dd yyyy-MM-dd
	 * @throws ParseException
	 */
	@ParameterizedTest
	@ValueSource(strings = {"2022/01/01","2022/01/31","2022-01-01","2022-01-31"
			 ,"2022/02/01","2022/02/28", "2022-02-01","2022-02-28"
			 ,"2022/03/01","2022/03/31", "2022-03-01","2022-03-31"
			 ,"2022/04/01","2022/04/30", "2022-04-01","2022-04-30"
			 ,"2022/05/01","2022/05/31", "2022-05-01","2022-05-31"
			 ,"2022/06/01","2022/06/30", "2022-06-01","2022-06-30"
			 ,"2022/07/01","2022/07/31", "2022-07-01","2022-07-31"
			 ,"2022/08/01","2022/08/31", "2022-08-01","2022-08-31"
			 ,"2022/09/01","2022/09/30", "2022-09-01","2022-09-30"
			 ,"2022/10/01","2022/10/31", "2022-10-01","2022-10-31"
			 ,"2022/11/01","2022/11/30", "2022-11-01","2022-11-30"
			 ,"2022/12/01","2022/12/31", "2022-12-01","2022-12-31"
			 ,"9999/12/31","9999-12-31"
			 })
	void 有効な日付チェック(String date) throws ParseException {
		assertTrue(dateCheck.checkDate(date));
	}

```
これで@ValueSourceで設定したパラメータ、50パターン全てに対しテストを行える。
テストに失敗したとき、どのパラメータで失敗したかの確認も行える。


@CsvSourceを使用する方法もある。
引数が複数あるメソッドをテストする際に使用する。
また、入力値と期待値を纏めてパラメータ化することもできる。
ただし記載が直感的に分かりづらく可読性が下がるため、使用には注意が必要。

```java:DateCheckTest.java
	/**
	 * CsvSourceを使用したテスト
	 * @throws ParseException
	 */
	@ParameterizedTest
	@CsvSource({"2022/01/01, true", "2022/01/31, true", "2022-01-01, true","2022-01-31, true"
			,"2022/02/01, true","2022/02/28, true", "2022-02-01, true","2022-02-28, true"
			})
	void 有効な日付チェック2(String date, boolean validDate) throws ParseException {
		assertEquals(validDate, dateCheck.checkDate(date));
	}
```

- 異常系
フォーマット違いの日付についてパラメータ化テストを行う。
正常系と同じ要領で@ValueSourceでパラメータ化する。

```java:DateCheckTest.java
	/**
	 * 例外　フォーマットの相違
	 */
	@ParameterizedTest
	@ValueSource(strings = {"20220101","20220131"
			 ,"20220201","20220228"
			 })
	void フォーマット相違による例外(String date) throws ParseException {
		ParseException ex = assertThrows(ParseException.class, () -> dateCheck.checkDate(date));
		assertEquals("有効な日付ではありません", ex.getMessage());
	}
```
例外メッセージの確認も含めてテストできる。

次にカレンダーに存在しない不正な日付をパラメータ化する。

```java:DateCheckTest.java	
    /**
	 * 例外　不正な日付
	 */
	@ParameterizedTest
	@ValueSource(strings = {"2022/01/32","2022/02/29"
			 ,"2022/03/32","2022/04/31"
			 ,"2022/05/32","2022/06/31"
			 ,"2022/07/32","2022/08/32"
			 ,"2022/09/32","2022/10/32"
			 ,"2022/11/32","2022/12/32"
			 })
	void 不正な日付(String date) throws ParseException {
		ParseException ex = assertThrows(ParseException.class, () -> dateCheck.checkDate(date));
		assertEquals("有効な日付ではありません", ex.getMessage());
	}
```

桁数チェック、空チェック、nullチェックのテストケースも作成する。
nullは@ValueSourceでパラメータ化できない。

```java:DateCheckTest.java	
    @Test
	void 桁数の足りない日付() throws ParseException {
		assertFalse(dateCheck.checkDate("01/01"));
	}

	@Test
	void 日付が空() throws ParseException {
		assertFalse(dateCheck.checkDate(""));
	}

	@Test
	void 日付がnull() throws ParseException {
		assertFalse(dateCheck.checkDate(null));
	}
```

## まとめ
パラメータ化することでひとつのテストケースで複数のパラメータについてテストできるため、コードの可読性が上がる。
パラメータを追加する場合も、@ValueSourceにパラメータを追加するだけでよいので、変更容易性も上がる。

## 参考
[JUnit実践入門 ~体系的に学ぶユニットテストの技法](https://www.amazon.co.jp/JUnit%E5%AE%9F%E8%B7%B5%E5%85%A5%E9%96%80-%E4%BD%93%E7%B3%BB%E7%9A%84%E3%81%AB%E5%AD%A6%E3%81%B6%E3%83%A6%E3%83%8B%E3%83%83%E3%83%88%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E6%8A%80%E6%B3%95-WEB-PRESS-plus/dp/477415377X
)