こんにちは、かつコーチです。
「同じロジックを、入力値だけ変えて何パターンもテストしたい」というとき、@Testメソッドをコピペして値だけ変えていませんか。
今回はJUnit5のパラメータ化テスト(同じテストロジックを異なる入力値で繰り返し実行できる仕組み)の使い方を解説します。
パラメータ化テストとは?
なぜパラメータ化テストが必要なのか
例えば「偶数判定メソッド」をテストする場合、次のように似たテストを何個も書くことになりがちです。
// ❌Before:似たテストメソッドをコピペで量産している
@Test
void isEven_2は偶数() {
assertTrue(NumberUtil.isEven(2));
}
@Test
void isEven_4は偶数() {
assertTrue(NumberUtil.isEven(4));
}
@Test
void isEven_3は偶数ではない() {
assertFalse(NumberUtil.isEven(3));
}
テストケースが増えるたびにメソッドが増え続け、修正が必要になったときに全メソッドを直す羽目になります。
JUnit5のパラメータ化テストを使えば、1つのテストメソッドに複数の入力値を渡して繰り返し実行できます。
依存関係の追加
パラメータ化テストはjunit-jupiter-paramsというモジュールに含まれています。
Mavenの場合は次を追加します。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
Gradleの場合は次を追加します。
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.2'
junit-jupiterをまとめて依存に加えている場合は、junit-jupiter-paramsも自動的に含まれているため個別追加は不要です。
基本の書き方・実装手順
手順1:@ValueSourceで単純な値のリストを渡す
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
class NumberUtilTest {
@ParameterizedTest
@ValueSource(ints = {2, 4, 6, 100})
void isEven_偶数を正しく判定できる(int number) {
assertTrue(NumberUtil.isEven(number));
}
}
@Testの代わりに@ParameterizedTestを付け、@ValueSourceで渡したい値のリストを指定します。
このコードは1つのメソッドで4パターンのテストが自動的に実行され、それぞれ個別のテスト結果として表示されます。
手順2:@CsvSourceで複数の引数の組み合わせを渡す
入力値と期待値のように、複数の値を1セットとして渡したい場合は@CsvSourceを使います。
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@ParameterizedTest
@CsvSource({
"2, 3, 5",
"0, 0, 0",
"-1, 1, 0",
"100, 200, 300"
})
void add_2つの整数を正しく足し算できる(int a, int b, int expected) {
Calculator calculator = new Calculator();
assertEquals(expected, calculator.add(a, b));
}
}
カンマ区切りの1行が1テストケースに対応し、値は順番にメソッドの引数へ渡されます。
「入力2つ+期待値1つ」のように複数の値をセットで扱いたいテストは、@CsvSourceが最もよく使われる書き方です。
手順3:@MethodSourceでオブジェクトを含む複雑なデータを渡す
文字列や数値だけでなく、オブジェクトを渡したい場合は@MethodSourceを使ってメソッドからデータを供給します。
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
class UserServiceTest {
static Stream<User> provideUsers() {
return Stream.of(
new User(1L, "かつコーチ"),
new User(2L, "テストユーザー")
);
}
@ParameterizedTest
@MethodSource("provideUsers")
void getDisplayName_ユーザー名がそのまま表示名になる(User user) {
assertEquals(user.getName(), UserService.getDisplayName(user));
}
}
@MethodSourceには供給元メソッドの名前を文字列で指定し、そのメソッドがStreamやListでテストデータを返す形にします。
複雑なオブジェクトの組み合わせをテストしたいときは、この方法が最も柔軟です。
つまずきやすい設定・注意点
@MethodSourceで指定するメソッドは、原則としてテストクラス内のstaticメソッドにする必要があります。
インスタンスメソッドにしてしまうとエラーになるため、staticの付け忘れには注意してください。
よくあるつまずきポイント・エラー対処
@CsvSourceで空文字とnullの区別ができなかった
実際に私が業務でつまずいたのは、空文字のテストケースを@CsvSourceに含めようとしたときです。
// ❌Before:空文字のつもりが引数に何も渡されずエラーになる
@ParameterizedTest
@CsvSource({
"hello, 5",
", 0"
})
void length_文字列の長さを取得できる(String input, int expected) {
assertEquals(expected, StringUtil.length(input));
}
2行目の", 0"は空文字を渡したつもりでしたが、実行するとinputにnullが渡されてしまい、StringUtil.length(null)がNullPointerExceptionを投げてテストが落ちました。@CsvSourceはデフォルトで空の項目をnullとして扱う仕様になっているためです。
// ✅After:emptyValueオプションで空文字として扱う
@ParameterizedTest
@CsvSource(value = {
"hello, 5",
"'', 0"
}, delimiter = ',')
void length_文字列の長さを取得できる(String input, int expected) {
assertEquals(expected, StringUtil.length(input));
}
明示的に空文字を渡したい場合はシングルクォートで''と書くか、@CsvSourceのemptyValueオプションで挙動を指定します。nullと空文字は区別して扱う必要があるテストでは、この仕様を知らないと原因不明のエラーに悩まされるので注意してください。
応用・一歩先の使い方
@EnumSourceでenumの全パターンを網羅する
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import static org.junit.jupiter.api.Assertions.assertNotNull;
enum Status { ACTIVE, INACTIVE, PENDING }
@ParameterizedTest
@EnumSource(Status.class)
void getLabel_全ステータスでラベルが取得できる(Status status) {
assertNotNull(StatusUtil.getLabel(status));
}
@EnumSourceを使うと、enumの全定数を自動的にテストケースとして展開してくれます。
enumに新しい定数を追加したときも、テストコード側を修正せずに新しい定数が自動でテスト対象になるのが利点です。
@ParameterizedTestの表示名をカスタマイズする
@ParameterizedTest(name = "{0} + {1} = {2}")
@CsvSource({
"2, 3, 5",
"0, 0, 0"
})
void add_2つの整数を正しく足し算できる(int a, int b, int expected) {
assertEquals(expected, new Calculator().add(a, b));
}
name属性で{0}・{1}のようにプレースホルダーを使うと、テスト結果一覧に「2 + 3 = 5」のように具体的な値が表示されます。
テストケースが多くなるほど、どのパターンが失敗したのか一目で分かるようになるため実務では積極的に活用したい機能です。
まとめ
この記事のポイント
- パラメータ化テストは同じロジックを複数の入力値で繰り返し検証でき、テストメソッドの重複を防げる
- 単純な値のリストは
@ValueSource、複数引数の組み合わせは@CsvSource、複雑なオブジェクトは@MethodSourceを使う @CsvSourceでは空の項目がデフォルトでnull扱いになる点に注意する@EnumSourceはenumの全パターンを自動網羅でき、定数追加時のテスト漏れを防げる
次に読むべき記事
- JUnit5の基本
- Mockitoでモックを使う
タグ: Java, 中級者向け, テスト