お題
テストコードを書いている時に、(例えば、入力値を全て大文字にして返す関数に対して)下記のようなケースを用意したとする。
入力値 | 期待結果 |
---|---|
z | Z |
test | TEST |
Abc | ABC |
null | null |
"" | "" |
あ | あ |
このような「入力値」と「期待結果」のパターンがたくさんあるものを1テストケースずつ分けて書いていくのは、けっこうしんどいので、そういったものをまとめてテストできる”パラメータ化テスト”という方法がある。
JavaのテストでおなじみのJUnitでもパラメータ化テストを書く専用の書き方があるのだけど、ちょっと複雑なパターンになると可読性が落ちるのと、テストの途中で失敗した時に、どこで失敗したのかがとても追いづらい。
そんな問題をSpockが見事に解決してくれるので、紹介してみる。
(Spock自体の登場は何年も前のことなので大分使い古しのツールだけど、いまだにこの手のテストでは強力なものとも言える)
開発環境
# OS
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"
# Java
$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
# IDE
みんな大好きIntelliJ IDEA
実践
今回のソースは↓
https://github.com/sky0621/try-spock-java8
必要な依存ライブラリは「JUnit4」と「Spock」だけ。
https://github.com/sky0621/try-spock-java8/blob/master/try-spock-java8/pom.xml
■あるコード値を検証するメソッドのテストケースを作成
テスト対象のクラス
package com.example;
public class SomeCode {
private String code;
public SomeCode(String code) {
this.code = code;
}
public boolean validate() {
if (this.code == null) {
return true;
}
if (this.code.equals("")) {
return true;
}
if (this.code.length() != 3) {
return false;
}
if (!this.code.startsWith("C")) {
return false;
}
return true;
}
}
JUnit4のパラメータ化テストの書き方
package com.example;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class SomeCodeJUnitTest {
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
// コード、バリデーション結果
{null, true},
{"", true},
{"C01", true},
{"C001", false},
{"A01", false},
});
}
private String コード;
private boolean バリデーション結果;
public SomeCodeJUnitTest(String コード, boolean バリデーション結果) {
this.コード = コード;
this.バリデーション結果 = バリデーション結果;
}
@Test
public void 与えられたコードに対するバリデーション結果が返る() {
assertEquals(this.バリデーション結果, new SomeCode(this.コード).validate());
}
}
まあ、このくらいの分量なら、まだ見れる。
Spockを使った書き方
package com.example
import spock.lang.Specification
class SomeCodeTest extends Specification {
def "与えられたコードに対するバリデーション結果が返る"() {
expect:
new SomeCode(コード).validate() == バリデーション結果
where:
コード | バリデーション結果
null | true
"" | true
"C01" | true
"C001" | false
"A01" | false
}
}
正直、初見で説明受けなくても何をテストしたいかわかる。
■与えられた文字列配列の中から指定のワードを含むもののみ抽出するメソッドのテストケースを作成
テスト対象のクラス
package com.example;
import java.util.List;
public class PgLang {
public static String[] findMatchWords(List<String> pgList, String searchWord) {
if (pgList == null) {
return null;
}
if (searchWord == null) {
return null;
}
return pgList.stream().filter(pg -> pg.contains(searchWord)).toArray(String[]::new);
}
}
JUnit4のテストケースは省略。
Spockを使った書き方
package com.example
import spock.lang.Specification
class PgLangTest extends Specification {
def "与えられたプログラミング言語の中から探索ワードを含む言語のみ返す"() {
expect:
PgLang.findMatchWords(プログラミング言語群, 探索ワード) == 結果群
where:
プログラミング言語群 | 探索ワード | 結果群
["Java", "C++", "Go", "C"] | "C" | ["C++", "C"]
["Ruby", "Elixir", "Python"] | "y" | ["Ruby", "Python"]
["Scala", "Groovy", "Kotlin"] | "oo" | ["Groovy"]
null | "J" | null
["C#", "JavaScript"] | null | null
["PHP", "Closure", "Lisp"] | "" | ["PHP", "Closure", "Lisp"]
}
}
まとめ
テストケースが多くなってくると辛くなってくるパラメータ化テストについては、Spockを使うと誰でもひと目でわかるテストコードになるんじゃないかと思う。
参考