何にも依存しない一番シンプルな繰り返しテストと、
Junit5のParameterizedTestの実装方法について説明します。
テスト対象の説明
三角形の面積を求めるというシンプルなロジックと、
入力値を特定の値しか認めないロジック。
DemoService
package com.example.demo.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
class DemoService {
int calculateTriangleArea(int base, int height) {
return (base * height) / 2;
}
boolean validation(int height){
return height == 3 || height == 5;
}
}
最初のテストはこんな感じです。
@Test
void normalTest() {
//GIVEN
int base = 6;
int height = 3;
int excepted = 9;
//WHEN
int actual = target.calculateTriangleArea(base, height);
//THEN
assertThat(actual).isEqualTo(excepted);
}
DataPattern実装
@Test
void dataPatternTest() {
//GIVEN
for (DataPattern param : List.of(
new DataPattern(6, 3, 9),
new DataPattern(9, 4, 18),
new DataPattern(7, 4, 14)
)) {
//WHEN
int actual = target.calculateTriangleArea(param.base, param.height);
//THEN
assertThat(actual).isEqualTo(param.excepted);
}
}
class DataPattern {
int base;
int height;
int excepted;
DataPattern(int base, int height, int excepted) {
this.base = base;
this.height = height;
this.excepted = excepted;
}
}
パラメータ格納用のインナークラスを作り、テストメソッド上でまとめて生成したものを一気にテストしています。
実行結果
ParameterizedTest実装
Junit5で可能なテストのやり方です。
ParameterizedTestの実装方法として、パラメータの渡し方で複数のやり方があります。
Junit5の準備
jarを追加します。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
- junit-jupiter-params
ParameterizedTestで使用するアノテーションを有効にします - junit-platform-commons
ParameterizedTestでの値の渡し方の一つである、
CsvSourceで必要となります。 - junit-jupiter
Junit5を使えるようにします。
@ArgumentsSource
DataPattern実装のようにパラメータ用のクラスファイルを作成し、
@ArgumentsSourceの引数でクラスごと渡します。
パラメータ用のクラスにはArgumentProviderをimplementsします。
@ParameterizedTest
@ArgumentsSource(ArgumentsParameterTest.class)
void ArgumentsParameterTest(ArgumentsParameterTest args) {
//WHEN
int actual = target.calculateTriangleArea(args.base, args.height);
//THEN
assertThat(actual).isEqualTo(args.expected);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
private static class ArgumentsParameterTest implements ArgumentsProvider {
int base;
int height;
int expected;
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return Stream.of(
Arguments.of(new ArgumentsParameterTest(6, 2, 6)),
Arguments.of(new ArgumentsParameterTest(9, 4, 18)),
Arguments.of(new ArgumentsParameterTest(7, 4, 14))
);
}
}
パラメータ用のクラスはstaticクラスにする必要があります。
オブジェクトのパラメータを渡したい時や、ちょっと複雑なAssertionをかけたい時に使えます。
もちろんパラメータとして渡したクラスのメソッドも使えます。
実行結果
ビルダーパターンを使ってパラメータを生成すると以下の感じです。
@ParameterizedTest
@ArgumentsSource(ArgumentsParameterTestBuilder.class)
void ArgumentsParameterTestBuilder(ArgumentsParameterTestBuilder args) {
//WHEN
int actual = target.calculateTriangleArea(args.base, args.height);
//THEN
assertThat(actual).isEqualTo(args.expected);
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
private static class ArgumentsParameterTestBuilder implements ArgumentsProvider {
int base;
int height;
int expected;
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return Stream.of(
Arguments.of(ArgumentsParameterTestBuilder.builder().base(6).height(2).expected(6).build()),
Arguments.of(ArgumentsParameterTestBuilder.builder().base(9).height(4).expected(18).build()),
Arguments.of(ArgumentsParameterTestBuilder.builder().base(7).height(4).expected(14).build())
);
}
}
@MethodSource
パラメータ用のメソッドを作成して、テストメソッドの引数に渡します。
@ParameterizedTest
@MethodSource("methodParameter")
void methodParameterTest(int base, int height, int expected) {
//WHEN
int actual = target.calculateTriangleArea(base, height);
//THEN
assertThat(actual).isEqualTo(expected);
}
static Stream<Arguments> methodParameter() {
return Stream.of(
Arguments.of(6, 2, 6),
Arguments.of(9, 4, 18),
Arguments.of(7, 4, 14)
);
}
オブジェクトを渡すこともできます。
また@ArgumentsSourceではNestされたクラスの中にはパラメータクラスを作ることはできませんが、
@MethodSourceはTestInstanceを使うことでNestクラス内にも実装できます。
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class nested {
@ParameterizedTest
@MethodSource("methodParameterNest")
void methodParameterTestNested(int base, int height, int expected) {
//WHEN
int actual = target.calculateTriangleArea(base, height);
//THEN
assertThat(actual).isEqualTo(expected);
}
Stream<Arguments> methodParameterNest() {
return Stream.of(
Arguments.of(6, 2, 6),
Arguments.of(9, 4, 18),
Arguments.of(7, 4, 14)
);
}
}
実行結果
@CsvSource
複数のパラメータをアノテーションの中でCsv形式で渡すことができます。
オブジェクトは渡すことができません。
パラメータの型は暗黙的に変換されます。
日付だったら「yyyy-MM-dd」とか。
@ParameterizedTest
@CsvSource({
"6,2,6",
"9,4,18",
"7,4,14"
})
void CsvParameterTest(int base, int height, int expected) {
//WHEN
int actual = target.calculateTriangleArea(base, height);
//THEN
assertThat(actual).isEqualTo(expected);
}
Nestクラスの中でも使えます。
実行結果
@ValueSource
一つのパラメータを渡すことができます。
オブジェクトは渡すことができません。
@ParameterizedTest
@ValueSource(ints = {1, 2, 4, 6})
void ValueParameterTestValue(int height) {
//WHEN
boolean actual = target.validation(height);
//THEN
assertThat(actual).isFalse();
}
ParameterizedTestの中では一番シンプルです。
バリデーションチェックのテストとかする時に使います。
Nestクラスの中でも使えます。
このテストだけDemoServiceのvalidationメソッドに対してテストをしています。
実行結果
まとめ
大概のテストは全部@MethodSourceでやってしまえると思っています。
もっと簡単にできそうだと思ったら@CsvSourceか@ValueSourceで。
@ArgumentsSourceはテストの実装自体もちょっとパワー使いますね。