unit-test-parameterized について
Java 開発者向けに、JUnit 5 のパラメーター化テストを扱うスキルです。単純値、表形式データ、複雑なオブジェクト、列挙型のデータソースに加え、表示名や検証方法も説明します。
主な機能
- 016 種類の JUnit 5 データソースのパターン
- 02読みやすいテスト表示名の設定
- 03データソースとメソッド引数の型合わせ
- 04境界値とシナリオのテスト手法
- 05テスト実行と依存関係の確認
活用シーン
- 01複数の入力パターンのテスト
- 02Java の境界値の確認
- 03重複するテストロジックの集約
- 04列挙型やオブジェクトのシナリオ検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill unit-test-parameterizedインストール後、Claude Code で/unit-test-parameterizedを入力して読み込まれたか確認します。
詳細
JUnit 5 によるパラメーター化ユニットテスト
概要
JUnit 5 を使用した Java のパラメーター化ユニットテストのパターンを紹介します。@ValueSource、@CsvSource、@MethodSource、@EnumSource、@ArgumentsSource、カスタム表示名について説明します。同じテストロジックを複数の入力値で実行することで、テストの重複を減らします。
使用する場面
- 複数の入力の組み合わせを扱う JUnit テストを作成する場合
- Java でデータ駆動テストを実装する場合
- 同じテストを異なる値で実行する場合(境界値分析)
- 1 つのテストメソッドから複数のシナリオをテストする場合
手順
- 依存関係を追加する:
junit-jupiter-paramsがテストクラスパスに含まれていることを確認します(junit-jupiterに含まれています)。 - ソースを選択する:単純な値には
@ValueSource、表形式のデータには@CsvSource、複雑なオブジェクトには@MethodSourceを使用します。 - パラメーターを一致させる:テストメソッドのパラメーターは、データソースの型と一致させる必要があります。
- 表示名を設定する:読みやすい出力にするには、
name = "{0}..."を使用します。 - 検証する:
./gradlew test --infoまたはmvn testを実行し、すべてのパラメーターの組み合わせが実行されることを確認します。
例
Maven / Gradle の依存関係
JUnit 5 のパラメーター化テストには junit-jupiter が必要です(params が含まれています)。アサーション用に assertj-core を追加します。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
// Gradle
testImplementation("org.junit.jupiter:junit-jupiter")
@ValueSource — 単純な値
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.*;
@ParameterizedTest
@ValueSource(strings = {"hello", "world", "test"})
void shouldCapitalizeAllStrings(String input) {
assertThat(StringUtils.capitalize(input)).isNotEmpty();
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void shouldBePositive(int number) {
assertThat(number).isPositive();
}
@ParameterizedTest
@ValueSource(ints = {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE})
void shouldHandleBoundaryValues(int value) {
assertThat(Math.incrementExact(value)).isGreaterThan(value);
}
@CsvSource — 表形式のデータ
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
@ParameterizedTest
@CsvSource({
"[email protected], true",
"[email protected], true",
"invalid-email, false",
"user@, false",
"@example.com, false"
})
void shouldValidateEmailAddresses(String email, boolean expected) {
assertThat(UserValidator.isValidEmail(email)).isEqualTo(expected);
}
@MethodSource — 複雑なデータ
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
@ParameterizedTest
@MethodSource("additionTestCases")
void shouldAddNumbersCorrectly(int a, int b, int expected) {
assertThat(Calculator.add(a, b)).isEqualTo(expected);
}
static Stream<Arguments> additionTestCases() {
return Stream.of(
Arguments.of(1, 2, 3),
Arguments.of(0, 0, 0),
Arguments.of(-1, 1, 0),
Arguments.of(100, 200, 300)
);
}
@EnumSource — Enum の値
@ParameterizedTest
@EnumSource(Status.class)
void shouldHandleAllStatuses(Status status) {
assertThat(status).isNotNull();
}
@ParameterizedTest
@EnumSource(value = Status.class, names = {"ACTIVE", "INACTIVE"})
void shouldHandleSpecificStatuses(Status status) {
assertThat(status).isIn(Status.ACTIVE, Status.INACTIVE);
}
カスタム表示名
@ParameterizedTest(name = "Discount of {0}% should be calculated correctly")
@ValueSource(ints = {5, 10, 15, 20})
void shouldApplyDiscount(int discountPercent) {
double result = DiscountCalculator.apply(100.0, discountPercent);
assertThat(result).isEqualTo(100.0 * (1 - discountPercent / 100.0));
}
カスタム ArgumentsProvider
class RangeValidatorProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of(0, 0, 100, true),
Arguments.of(50, 0, 100, true),
Arguments.of(-1, 0, 100, false),
Arguments.of(101, 0, 100, false)
);
}
}
@ParameterizedTest
@ArgumentsSource(RangeValidatorProvider.class)
void shouldValidateRange(int value, int min, int max, boolean expected) {
assertThat(RangeValidator.isInRange(value, min, max)).isEqualTo(expected);
}
エラー条件のテスト
@ParameterizedTest
@ValueSource(strings = {"", " ", null})
void shouldThrowExceptionForInvalidInput(String input) {
assertThatThrownBy(() -> Parser.parse(input))
.isInstanceOf(IllegalArgumentException.class);
}
ベストプラクティス
- 説明的な表示名を使用します。読みやすい出力にするには、
name = "{0}..."を指定します。 - 境界値をテストします。最小値、最大値、ゼロ、エッジケースを含めます。
- テストロジックを絞り込みます。パラメーターセットごとにアサーションを 1 つにします。
- 複雑なオブジェクトには
@MethodSource、表形式のデータには@CsvSourceを使用します。 - テストデータを論理的に整理します。関連するシナリオをまとめます。
制約と警告
- パラメーター数を一致させる必要があります:ソースから渡されるパラメーター数は、テストメソッドのシグネチャと一致する必要があります。
@ValueSourceの制限:プリミティブ型、文字列、Enum のみをサポートします。オブジェクトや null は直接指定できません。- CSV のエスケープ:カンマを含む文字列は、
@CsvSourceでシングルクォートを使用する必要があります。 @MethodSourceの可視性:ファクトリーメソッドは、同じテストクラス内で static にする必要があります。- 表示名のプレースホルダー:パラメーターを参照するには、
{0}、{1}などを使用します。 - 実行回数:各パラメーターセットは、個別のテスト呼び出しとして実行されます。
参考資料
コメント
Markdown 対応