きり丸の技術日記

技術・エンジニアのイベント・資格等はこちらにまとめる予定です

パラメータだけ変えて中身は同じテストをしたい(ParameterizedTest)

Javaでもっといいテストが書きたい!
ということで、Junit5のParameterizedTestに注目して調査しました。
正直、どんな機能が眠っているのかよくわかっていないので、もしオススメの機能があれば教えていただきたいです。

なお、結論については、自分の主観が大いに含まれています。

環境

結論

ParameterizedTestならArgumentsSourceを使おう!

理由

  • テスト用のクラスが使える。
    • 複数パラメータを指定できるので、条件と結果を一覧化できる。
    • 渡したパラメータが表示できるので、失敗したときにどの条件が失敗したかIDEで一目瞭然。(重要!)
      • CI環境でもっといい表示ができればグッドだけど、イマイチわかってない。
  • builderクラスが使えるので、後々条件がわかりやすい。

懸念点

  • ArgumentsSourceはenumSourceやvalueSources等の継承元になっているから、本来は継承先のほうが使いやすいはず。
    それなのに、ArgumentsSourceを使いやすいと感じているということは、自分が知らない情報があるのでは…?
    という不安。

実行結果のメッセージ

画像の通り、ArgumentsSourceの実行結果が後に失敗した際に条件が一番わかりやすい形で出力されています。
テストはリファクタリングの準備も兼ねているので、エラーメッセージがわかりやすいことも重要なファクターです。

f:id:nainaistar:20190808225719p:plain
errorMessage

ソースコードは、ボードゲームでその位置に駒が置けるかという挙動を示しています。

  @ParameterizedTest
  @ArgumentsSource(Taiueo.class)
  void fuga(Taiueo taiueo) {

    assertEquals(taiueo.result, target.canSetPlayerPiece(taiueo.player, taiueo.row, taiueo.column));
  }

  @Data
  @Builder
  @NoArgsConstructor
  @AllArgsConstructor
  static class Taiueo implements ArgumentsProvider {
    private int player;
    private int row;
    private int column;
    private boolean result;

    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
      return Stream.of(
          Taiueo.builder().player(1).row(0).column(0).result(false).build()
      ).map(Arguments::of);
    }
  }

その他のXXXSourceの不採用理由

valueSource

  • 単一項目しか使えない。
  • 簡単なテストには便利だが、拡張性はない。
  @ParameterizedTest
  @ValueSource(strings = {"1", "2", "3"})
  void hoge3(String param) {
    assertEquals("1", param);
  }

MethodSource

  • MethodSourceとパラメータの紐づけが一目瞭然ではない。
  • staticなメソッドにする必要があるため、builderクラスが使えない。
  @ParameterizedTest
  @MethodSource("aiueo")
  void hoge(int player, int row, int column, boolean result) {
    assertEquals(result, target.canSetPlayerPiece(player, row, column));
  }

  private static Stream<Arguments> aiueo() {
    return Stream.of(
        Arguments.of(1, 0, 0, false)
    );
  }

EnumSource

  • テスト結果がenumの名称になってしまうため、IDE上では失敗したときの条件がわかりづらい。
    • ちゃんとした名称を付けることでちゃんとしたテストにはなるが、個人的にそこにコストはかけたくない。
  • modeがあるため、複雑なテストになる可能性がある。
  @ParameterizedTest
  @EnumSource(Saiueo.class)
  void hige(Saiueo saiueo) {

    assertEquals(saiueo.result, target.canSetPlayerPiece(saiueo.player, saiueo.row, saiueo.column));
  }

  enum Saiueo {
    A(1, 0, 0, false),
    B(1, 0, 1, false),
    ;

    private final int player;
    private final int row;
    private final int column;
    private final boolean result;

    Saiueo(int player, int row, int column, boolean result) {
      this.player = player;
      this.row = row;
      this.column = column;
      this.result = result;
    }
  }

参考にした記事