きり丸の技術日記

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

もっともっといいテストが書きたい(AssertAllをもっと使う)

もっともっといいテストが書きたい(AssertAllをもっと使う)

下記の記事を書いて、
「テストで必要なことを全て書いた!
あとは、テストに慣れるだけだ!」

と、思ってたのですが、思ったよりもassertAllを使いたい場面が増えてきたので、自分用へのメモを残します。

過去記事

対象者

  • Junit5の初心者
  • テストを途中で止めずに一気に評価したい人

結論

  • assertThat で表現できないことを assertAll で表現しよう
  • ライブラリを超えて一気に Assertion したければ assertAll でやろう
  • 仕様変更時に影響範囲のテストを一気に直せるのもメリット

経緯

今の現場では以下で Assertion をしています。

  • DBunit (動作後のDBの確認)
  • Mockito (呼び出し回数及びモック)
  • mockMvc (Controller層_インフラストラクチャ層のモック)
  • wiremock (外部サービスのモック)
  • assertEquals や assertThat

mockMvc は andExpect を使用して、HttpStatusとBodyを一気に表現できるのですが、
Mockito は一気に表現できるようなメソッドが用意されていません。
(もし、上記ライブラリで一気に表現できるメソッドがあれば教えてください…)

mockMvcの例

@Autowired
private MockMvc mockMvc;

@Test
void booksGet() throws Exception {

    String expectJson = "{\n" +
            "  \"isbns\": null\n" +
            "}";

    mockMvc.perform(get("/v1/books"))
            .andExpect(status().isOk())
            .andExpect(content().json(expectJson, true));
}

ただ、当然ながら(?)ライブラリを超えて、 Assertion はできません。
そうなると、必然的にライブラリごとの Assertion を行うことになり、テストを完了させるのに時間がかかってしまいます。
下の例では、mockMvc と Mockito だけですが、これが上で挙げた DBunit や wireMock も含めるとかなりの時間を消費してしまいます。

@Autowired
private MockMvc mockMvc;
@MockBean
public BookService service;

@Test
void booksGet() throws Exception {

    String expectJson = "{\n" +
            "  \"isbns\": null\n" +
            "}";

    Mockito.when(service.apply(Mockito.any())).thenReturn(null);

    // mockMvcのAssert
    mockMvc.perform(get("/v1/books"))
            .andExpect(status().isOk())
            .andExpect(content().json(expectJson, true));
    
    // MockitoのAssert
    Mockito.verify(service).apply(null);
}

なので、上記ライブラリを越境できるように、AssertAllを使用します。
そうすることで、各ライブラリの Assertion を行いつつ、途中でテストを止めないので、何が悪かったのかを一目で見ることができます。

こうすると、機能変更時にも強くなり、一気に変更箇所の影響範囲を見つけることができます。
TDDを行っていても、一気に全部のテストを直してから回すわけではないと思うので、修正対象のテストと対象がどこかを一気に見つけられるのは大事だと思います。

@Autowired
private MockMvc mockMvc;
@MockBean
public BookService service;

@Test
void booksGet() throws Exception {

    String expectJson = "{\n" +
            "  \"isbns\": null\n" +
            "}";

    Mockito.when(service.apply(Mockito.any())).thenReturn(null);

    // mockMvc と Mockito のAssert
    assertAll(
        () -> mockMvc.perform(get("/v1/books"))
            .andExpect(status().isOk())
            .andExpect(content().json(expectJson, true)),
        () -> Mockito.verify(service).apply(null)
    );
}

そもそも、なんで、一気に評価したいかというと、毎回毎回のテストが遅いからです。

@SpringBootTest は 毎回色んな値をDIしに行くので、遅くなりがちです。
20-30秒で済むならともかく、仕事では2-3分かかっているので、可能な限り一気にAssertionしたいのです。

assertAll は 影響範囲のある行を丸めてしまうので、好きではないのですが、独立してテストを行ってくれるので、その点はとても便利です。

assertAll が失敗した行、または失敗した変数(Execution)を指定してくれるなら、非常にありがたいのですが…。
…これは、OSSへのコミットチャンスなんですかね?

以上です。最後にまた結論を記載します。

結論

  • assertThat で表現できないことを assertAll で表現しよう
  • ライブラリを超えて一気に Assertion したければ assertAll でやろう
  • 仕様変更時に影響範囲のテストを一気に直せるのもメリット