ユニットテストの設計哲学
プログラムはまず人間が読むために書かれ、その結果として機械が実行できるようになる。
Programs must be written for people to read, and only incidentally for machines to execute.
そして、プログラミングの第一歩は命名です。ユニットテストも同様です。
WWW
ユニットテストの命名をどのようにして人間が理解できるようにするか?命名には3つの要素を反映する必要があります。これを WWW 原則と呼びます。
- 何をテストするか?(what)
- どのような条件下でテストするか?(when)
- どのような結果を期待するか?(want)
以下に、MJGA スキャフォールドのユニットテスト例を示します。
@Test
void createVerifyGetSubjectJwt_givenUserIdentify_shouldReturnTrueAndGetExpectIdentify() {
String jwt = cookieJwt.createJwt("1");
assertThat(cookieJwt.verifyToken(jwt)).isTrue();
assertThat(cookieJwt.getSubject(jwt)).isEqualTo("1");
}
この命名を3つの部分に分解すると以下のようになります:
-
createVerifyGetSubjectJwt
は、テスト対象を示しています。 -
givenUserIdentify
は、テストの条件を示しています。 -
shouldReturnTrueAndGetExpectIdentify
は、期待される動作を説明しています。
これが良い命名です——ビジネス担当者でも理解できる命名です。
AAA
最初の関門は終わりました。次に、テストロジックを実装します。実は、WWW の命名設計があれば、ロジックの実装は既に道筋が立っています。命名と同様に、テストロジックにも参照すべき原則があります。通常、AAA 原則と呼ばれます。
- 準備(Arrange)
- 実行(Act)
- 検証(Assert)
MJGA スキャフォールドのテストケースを見てみましょう:
@Test
@WithMockUser
void signIn_givenValidHttpRequest_shouldSucceedWith200() throws Exception {
String stubUsername = "test_04cb017e1fe6";
String stubPassword = "test_567472858b8c";
SignInDto signInDto = new SignInDto();
signInDto.setUsername(stubUsername);
signInDto.setPassword(stubPassword);
when(signService.signIn(signInDto)).thenReturn(1L);
mockMvc
.perform(
post("/auth/sign-in")
.contentType(MediaType.APPLICATION_JSON)
.content(
"""
{
"username": "test_04cb017e1fe6",
"password": "test_567472858b8c"
}
""")
.with(csrf()))
.andExpect(status().isOk());
}
Arrange
これはテストの準備段階です。この段階では、コンテキストを提供するためのコードを構築します。例えば、データの挿入、オブジェクトの構築、さらにはより複雑な mock や stub もここに含まれます。このケースでは、SignInDto オブジェクトを構築し、signIn メソッドの戻り値を仮定しています——これらはすべて、後のテストの準備です。
Act
これは実行段階です。このケースでは mockMvc.perfom
に対応します。ここでは Api が複雑ですが、1行のコードと理解してください。
Assert
期待する結果を明確に示す必要があります。このケースでは .andExpect(status().isOk());
に対応します。
多くの場合、特定のロジックが正常に動作し、エラーが発生しないことを確認するためのテストメソッドを書く必要があります。メソッド自体に戻り値がないためです。これは簡単で、assertDoesNotThrow
を使用して解決できます。
// pause & resume job
JobKey firstDataBackupJobKey = dataBackupJobKeys.iterator().next();
assertDoesNotThrow(
() -> {
dataBackupScheduler.pauseJob(firstDataBackupJobKey);
dataBackupScheduler.resumeJob(firstDataBackupJobKey);
});
これらのコーディング哲学は、使用する言語やフレームワークに関係ありません。どんなユニットテストもこのような考え方で書かれています。より多くのユニットテストの内容については、私が後で整理して公開します。
最後に
- 私は Chuck1sn です。現代の Jvm エコシステムを長年推進している開発者です。
- あなたの返信、いいね、ブックマークは、私が継続的に更新する原動力です。
- 簡単なワンクリックでのサポートは、私にとって大きな励みになります。本当にありがとうございます!
- 私のアカウントをフォローして、最新の記事をいち早く受け取ってください。
PS:上記のすべてのコード例は Github リポジトリで見つけることができます。役に立った場合は、ぜひ Star を付けてください。それは私にとって大きな励みになります。ありがとうございます!