パラメータ化テストを行うか否か迷った話

テスト対象メソッド

下記のようなメソッドの引数に入出力のファイルPATHを受け取り、ファイル出力するメソッドのテストを作成した時のお話。
割りと紆余曲折があったのでメモがてらまとめます。
コードは書きやすいのでGroovyで記述していますが、Groovy知らない方でも問題なく読めるかと思います。

テスト対象のメソッド

上記したようにファイルパスを受け取り、そのファイルの中身を読み取り、ゴニョゴニョした後指定PATHに出力するメソッド。

public class Hoge {
  void create(input, output) {

    def message = input.text
  
    // ファイルの中の文字列をゴニョゴニョして出力
  
    output << message
  }
}

パラメータ化テストを行わない場合のテストケース

テストケース
public class HogeTest {
  @Rule
  public TemporaryFolder temporaryFolderRule = new TemporaryFolder()

  @Rule
  public TestName testNameRule = new TestName()

  /** resourcesフォルダのPATH */
  private static final resourcesPath = "test/resources"

  /**
   * 実行テストケースのメソッド名を元に入力値、期待値を作成しcreateメソッドをテストする
   */
  private void executeCreateMethodTest() {
    // setUp
    def sut = new Hoge()
    def input = new File("${resourcesPath}/${testNameRule.methodName}_input.txt")
    def output =  temporaryFolderRule.newFile(testNameRule.methodName)
    def expected = new File("${resourcesPath}/${testNameRule.methodName}_output.txt").text

    // Excercise
    sut.create(input, output)
    def actual = output.text
    
    // Verify
    assertThat(actual, is(expected))
  }

  @Test
  public void ○○_正常系() {
    executeCreateMethodTest()
  }

  @Test
  public void ××_正常系() {
    executeCreateMethodTest()
  }
}
test/resources
├○○_正常系_input.txt
├○○_正常系_output.txt
├××_正常系_input.txt
└××_正常系_output.txt
  • TemporaryFolderはその名の通り一時フォルダを利用するためのRule*1
  • TestNameもその名の通りメソッド名をテストケース内で使用することができるRule*2
メリット
  • テストケースの一覧がアウトラインで一目瞭然
  • テストケースを1件づつ実行可能
デメリット
  • メソッド名のみ異なる全く同じメソッドが乱立する
  • テストクラスが無駄に大きくなる
  • 1つのテストケースのテスト内容がメソッドを見ないとわからない
  • テストケースを追加しても件数が増えない(気分の問題)
疑問点
  • そもそも共通メソッドにしない方が良い?

パラメータ化テストにした場合

テストケース
@RunWith(Theories.class)
public class HogeTest {
  @Rule
  public TemporaryFolder temporaryFolderRule = new TemporaryFolder()

  @DataPoints
  public static String[] params = [
    "○○_正常系",
    "××_正常系",
  ]
  
  /** resourcesフォルダのPATH */
  private static final resourcesPath = "test/resources"

  @Theory
  public void 正常系の場合(String param) {
    // setUp
    def sut = new Hoge()
    def input = new File("${resourcesPath}/${param}_input.txt")
    def output =  temporaryFolderRule.newFile(param)
    def expected = new File("${resourcesPath}/${param}_output.txt").text
    def msg = "[${param}]のテストに失敗。"

    // Excercise
    sut.create(input, output)
    def actual = output.text

    // Verify
    assertThat(msg, actual, is(expected))
  }
}
test/resources
├○○_正常系_input.txt
├○○_正常系_output.txt
├××_正常系_input.txt
└××_正常系_output.txt
  • TemporaryFolderはその名の通り一時フォルダを利用するためのRule
  • Theories、Theory、DataPointsはパラメータ化テストを行うためのテストランナーとアノテーション*3
メリット
  • テストコードを見るだけでテスト内容が一目瞭然
  • テストケースの追加が簡単
デメリット
  • テストケースを1件づつ実行不可能
  • 1件失敗した場合にそれ以降のテストが実行されない
疑問点
  • 失敗時のメッセージが微妙

まとめ

それぞれメリットデメリットがあり、パラメータ化テストを行うことがベストになるとは限らない。
今回のケースだと、パラメータ化テストを行わない方がベターな気がしました。

理由としては、今回のテスト対象のメソッドがファイルの入出力なので、テスト追加時に追加したテストケースだけを実行できなかったのは結構ストレス。
同様に、1件のテストケース追加にもそれなりに手間がかかるので追加したのにテストケース数が変わらないのもテスト作ってて楽しくないなと感じた事ですかね。(テストは楽しく作って楽しく実行するものって誰か偉い人が言ってた。)

そもそも、テスト対象がこのような場合はパラメータ化テストを行おうとするのが間違いだったりするのかな?とも思ったり。
また、本題とは関係ないけどRuleは簡単に使用できたのでこれからも積極的に使っていく予定。*4