テストの構造パターン「AAA」を詳しく解説します。

AAAパターンとは

2001年にBill Wake氏が提唱したテストの構造パターンで、テストコードを「Arrange(準備)」「Act(実行)」「Assert(検証)」の3つのステップに分けて記述する方法です。

詳細についてはBill Wake氏の記事を参照ください。

参考: Bill Wake - AAA Pattern

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」の説明として、記事の内容と合致するものはどれか?

  1. テスト対象オブジェクトを設定する部分
  2. テスト対象オブジェクトに対して操作を実行する部分
  3. 操作の結果が期待通りであるかを確認する部分
  4. テストデータをデータベースにセットアップする部分

Q2. AAAパターンの本質的な価値として、記事で述べられているものはどれか?

  1. コードにコメントを付けること自体
  2. 「1テスト1検証」という規律を作ること
  3. テストの実行速度を上げること
  4. モックの使用を強制すること

Q3. 記事の「AAAパターンを採用していない場合」のコード例が抱えていた問題点はどれか?

  1. データベースへの接続に失敗する
  2. testUserCreationという1つのテストの中で、作成・更新・別ユーザー作成という3つの検証を行っている
  3. Arrangeの部分が存在しない
  4. Assertが1つも書かれていない