Comparatorのテストを通じて、意図が通じやすいテストコードを考える


4年前に自分が書いたテストコードを読んでいたところ、とあるComparatorのテストコードの意図が読み取りづらいことに気が付きました。テストの内容としては正しいといえば正しいのですが、テストを読んでも具体的なユースケースが想像できなかったのです。

プロダクトコードの正しさを保証してくれている分にはある程度良いテストコードですが、できれば後世のメンテナが意図を読み取れたほうが幸せになります。ということで、空き時間を見つけて書き直してみました。

本記事では旧来のテストコードと改善後のテストコードを紹介して、供養の場とさせていただきます。

プロダクトコード
----------

```java
public class TodayReportComparator implements Comparator<Report> {
  @Override
  public int compare(Report lhs, Report rhs) {
    // がんばり
  }
}
```

TodayReportComparatorは、今日付けのレポートを優先的に上位に表示するよう並べ替えるComparatorです。


旧テストコード
----------

まずは従来のテストからご紹介します。このテストでは、 `Comparator#compare` の実行結果を検証しています。

```java
public class TodayReportComparatorTest {

  private TodayReportComparator sut;

  @Before
  public void setUp() {
    sut = new TodayReportComparator();
  }

  @Test
  public void 今日付けのレポートが昨日付けのレポートの上位に来る() {
    final long now = System.currentTimeMillis();
    lhs = new Report(new Date(now));
    rhs = new Report(new Date(now - 24 * 60 * 60 * 1000));
    assertThat(sut.compare(lhs, rhs), lessThan(0)); // lhsが上位
  }

  @Test
  public void 今日付けのレポートが明日付けのレポートの上位に来る() {
    final long now = System.currentTimeMillis();
    lhs = new Report(new Date(now));
    rhs = new Report(new Date(now + 24 * 60 * 60 * 1000));
    assertThat(sut.compare(lhs, rhs), lessThan(0)); // lhsが上位
  }
}
```

### 良いところ

* 簡潔
* Comparator語（`lhs`, `rhs`）を使っていてComparatorのテストっぽさが出ている

### 悪いところ

* プラマイどっちが返ってくると順番がどっちになるのかいちいち覚えていない
    - コメントで補足してるけど上手く頭に入ってこない
* `Collections.sort` で使う予定しかないのにそのユースケースではテストが書いてない
* 結局どうなってほしいのか想像しづらい

どれにせよ、JavaSEの挙動くらい覚えておけよという話だったり、ちょっと考えれば想像できるだろという話ではあります。しかしレビューする側からすると、あまりそこに脳みそを使いたくないというのも正直なところです。

新テストコード
----------

新しく書き換えたほうのテストでは、実際の使い道である `Collections.sort` の結果がどうなってほしいのかという点に着目して書いています。

```java
public class TodayReportComparatorTest {

  private TodayReportComparator sut;

  @Before
  public void setUp() {
    sut = new TodayReportComparator();
  }

  @Test
  public void 今日付けのレポートが昨日付けのレポートの上位に来る() {
    // Given
    final long now = System.currentTimeMillis();
    Report todayReport = new Report(new Date(now));
    Report yesterdayReport = new Report(new Date(now - 24 * 60 * 60 * 1000));

    List<Report> actual = Arrays.asList(
        yesterdayReport,
        todayReport
    );

    List<Report> expected = Arrays.asList(
        todayReport, // 今日付けのレポートが上位になっている
        yesterdayReport
    );

    // When
    Collections.sort(actual, sut);

    // Then
    assertThat(actual, is(expected));
  }

  @Test
  public void 今日付けのレポートが明日付けのレポートの上位に来る() {
    // Given
    final long now = System.currentTimeMillis();
    Report todayReport = new Report(new Date(now));
    Report tomorrowReport = new Report(new Date(now + 24 * 60 * 60 * 1000));

    List<Report> actual = Arrays.asList(
        tomorrowReport,
        todayReport
    );

    List<Report> expected = Arrays.asList(
        todayReport, // 今日付けのレポートが上位になっている
        tomorrowReport
    );

    // When
    Collections.sort(actual, sut);

    // Then
    assertThat(actual, is(expected));
  }
}
```

現在の自分の好みで書いてみました。本来やりたかった「今日付けレポートを優先して上に持ってくる」という意図をテスト上で表現できているのではと思います。

### 良いところ

* 作者が意図していた使い道がそのまま書いてある
* compareの戻り値を覚えていない人でもテストが読める

### 悪いところ

* 冗長

おわりに
----------

現在の筆者はJavaを書く際にはTDDが習慣付いており、テストが書ける場合はテストを先に書くようになりました。

プロダクトコードの正しさだけでなく、使い勝手を表現したり洗練するのにも、テストコードは役立ちます。プロダクトコードの改善に役立つテストコードを書いていけるといいですね。