テストの構造パターン「AAA」を詳しく解説します。
AAAパターンとは
2001年にBill Wake氏が提唱したテストの構造パターンで、テストコードを「Arrange(準備)」「Act(実行)」「Assert(検証)」の3つのステップに分けて記述する方法です。
詳細についてはBill Wake氏の記事を参照ください。
AAAパターンの読み方は明記されていませんでしたが、個人的には「トリプルエーパターン」か「スリーエーパターン」と呼ばれるのかなと思っています。
Arrange(準備)
Bill Wake氏によると下記のように定義されています。
準備:テスト対象オブジェクトを設定します。テスト対象オブジェクトを、協力者で囲む必要がある場合があります。テスト目的の場合、これらの協力者はテストオブジェクト(モック、フェイクなど)でも、実際のオブジェクトでも構いません。
テストするにあたっての必要なデータやオブジェクトは実データでもモックでも問題はなく、テストの目的に応じて適切に準備すればよいと解釈しました。
もう少し具体的な例で言うと、テスト対象のモデルやコントローラを作成し、必要なデータをデータベースにセットアップすることが「Arrange」に該当します。
POSTやGETなどのパラメータやリクエストボディも「Arrange」に含まれると考えられます。
例えば、ユーザーモデルのテストであれば、テスト用のユーザーを作成するコードがここに含まれると考えられます。
Act(実行)
Bill Wake氏によると下記のように定義されています。
操作:オブジェクトに対して何らかの操作(ミューテーターを介して)を実行します。場合によっては、パラメータ(テストオブジェクトなど)を指定する必要があるかもしれません。
テスト対象のオブジェクトに対して実際に操作を行う部分が「Act」に該当すると考えられます。
ここも具体的な例で言うと、ユーザーモデルのテストであれば、ユーザーを作成する処理や更新する処理や実行するコードが「Act」に該当します。
Assert(検証)
Bill Wake氏によると下記のように定義されています。
主張:オブジェクト、その協力者、そのパラメータ、そして場合によっては(まれに!!)グローバル状態について主張します。
ここがちょっと理解できなかったのですが、要するに「Act」で行った操作の結果が期待通りであるかを確認する部分が「Assert」に該当すると考えられます。
具体的な例で言うと、ユーザーモデルのテストであれば、ユーザーが正しく作成されたか、更新されたかを確認するコードが「Assert」に含まれると考えられます。
AAAパターンのなにが良いのか
AAAパターンは「1テスト1検証」という規律を作ることができます。また、この規律によって下記の2点のメリットを得ることができます。
- 可読性の向上: Arrange、Act、Assertの順序でコードが整理されるため、テストが何を検証しているのかが一目でわかります。
- 保守性の向上: 各テストが独立し、余計な責務を持ち込まないため、テスト対象に変更があった場合でも影響範囲を特定しやすく、修正が容易になります。
コード比較
AAAパターンを採用/採用しない場合のコードの違いを比較してみます。
AAAパターンを採用していない場合
public function testUserCreation()
{
$userData = ['name' => 'John Doe', 'email' => 'john@example.com'];
$user = User::create($userData);
$this->assertDatabaseHas('users', $userData);
$user->update(['name' => 'Jane Doe']);
$this->assertEquals('Jane Doe', $user->fresh()->name);
$anotherUserData = ['name' => 'Foo Bar', 'email' => 'foo@example.com'];
$anotherUser = User::create($anotherUserData);
$this->assertDatabaseHas('users', $anotherUserData);
}testUserCreationという名前なのに、実際には作成・更新・別ユーザーの作成という3つの検証を1つのテストにまとめてしまっています。
また、準備・実行・検証のブロックが繰り返し登場するため、どのAssertがどのActに対応しているのか、コードを読み解かないとわからない状態になっています。
AAAパターンを採用した場合
AAAパターンでは「1つのテストは1つのArrange・1つのAct・1つのAssertで完結させる」ことを意識するため、上記のテストは自然と次のように分割されます。
public function testUserCreation()
{
// Arrange
$userData = ['name' => 'John Doe', 'email' => 'john@example.com'];
// Act
$user = User::create($userData);
// Assert
$this->assertDatabaseHas('users', $userData);
}
public function testUserUpdate()
{
// Arrange
$user = User::factory()->create(['name' => 'John Doe']);
// Act
$user->update(['name' => 'Jane Doe']);
// Assert
$this->assertEquals('Jane Doe', $user->fresh()->name);
}可読性・保守性は一目瞭然かと思います。
まとめ
AAAパターンは提唱から年月が経っていますが、現在でも多くのテストコードで採用されていることを考えると、有用性は高いと考えています。
AIコーディングとの相性もよさそうだなと思います。
AAAパターン以外にもGiven-When-Thenパターンなど、テストコードの構造を明確にする手法はいくつか存在するので、また機会があれば調べてみようと思います。
理解度チェック(AI生成)
以下の4択問題で、AAAパターンの理解度をチェックしてみましょう。答えは折りたたまれているので、クリックして確認してください。
Q1. 「Assert」の説明として、記事の内容と合致するものはどれか?
- テスト対象オブジェクトを設定する部分
- テスト対象オブジェクトに対して操作を実行する部分
- 操作の結果が期待通りであるかを確認する部分
- テストデータをデータベースにセットアップする部分
答えを見る
正解: 3. 操作の結果が期待通りであるかを確認する部分 「Arrange」が準備、「Act」が操作の実行にあたるのに対し、「Assert」はActで行った操作の結果が期待通りかを確認する部分です。
Q2. AAAパターンの本質的な価値として、記事で述べられているものはどれか?
- コードにコメントを付けること自体
- 「1テスト1検証」という規律を作ること
- テストの実行速度を上げること
- モックの使用を強制すること
答えを見る
正解: 2. 「1テスト1検証」という規律を作ること コメントはあくまで構造を可視化するための補助であり、本質的な価値は1つのテストが1つの検証で完結するという規律にあります。
Q3. 記事の「AAAパターンを採用していない場合」のコード例が抱えていた問題点はどれか?
- データベースへの接続に失敗する
testUserCreationという1つのテストの中で、作成・更新・別ユーザー作成という3つの検証を行っている- Arrangeの部分が存在しない
- Assertが1つも書かれていない
答えを見る
正解: 2.
testUserCreationという1つのテストの中で、作成・更新・別ユーザー作成という3つの検証を行っている テスト名と実際の検証内容が一致しておらず、準備・実行・検証のブロックが繰り返し登場するため、どのAssertがどのActに対応するのかが読み解かないとわかりにくい状態になっていました。