pytest でテストを書き始めると、「テストコードってどう構成すればいいの?」と悩むことがあります。そこで覚えておきたいのが Arrange-Act-Assert(AAAパターン) です。「準備 → 実行 → 確認」の3段構造で書くだけで、読みやすく・保守しやすいテストコードが書けるようになります。
📌 この記事の対象読者
| ✅ pytest を使い始めたばかりで、テストコードの書き方がわからない方 |
| ✅ テストを書いてみたが、読みにくい・何をテストしているかわかりにくいと感じる方 |
| ✅ 「良いテストコード」の基準を知りたい QA エンジニア・開発者 |
✅ pytest-guide に進む前に基礎を固めたい方 |
✅ この記事を読むと得られること
|
👨💻 この記事の信頼性について 本記事は QA エンジニアとして15年以上、テスト自動化に携わってきた筆者が執筆しています。「テストは書けるが、チームメンバーに読んでもらえない」という経験から、AAAパターンの重要性を実感し、現在の現場でも標準として採用しています。 |
📌 この記事の結論
|
テストコードを書き始めた頃、筆者もこんなコードを書いていました。
# ❌ 構造がなく、何をテストしているかわかりにくい
def test_calc():
assert add(1, 2) == 3
assert add(-1, 1) == 0
assert add(0, 0) == 0
result = add(100, 200)
assert result == 300
assert add(1.5, 2.5) == 4.0一見問題なさそうに見えますが、どれかが失敗したとき「何のテストが失敗したのか」すぐにわかりません。また複数の検証が混在しているため、テストの意図が伝わりにくいです。この問題を解決するのが Arrange-Act-Assert パターン です。
Arrange-Act-Assert(AAA)パターンとは
AAAパターンとは、テストコードを 3つのフェーズ に分けて書く構造のことです。どんなプログラミング言語・テストフレームワークでも使える普遍的なパターンで、テスト界隈では事実上の標準として広く使われています。
| Arrange 準備 | → | Act 実行 | → | Assert 検証 |
| フェーズ | 役割 | 具体的にやること |
|---|---|---|
| Arrange(準備) | テストに必要な状態を整える | 変数の定義、オブジェクト生成、mock の設定など |
| Act(実行) | テスト対象の処理を1回だけ実行する | 関数呼び出し、API リクエスト、ボタンクリックなど |
| Assert(検証) | 結果が期待通りかを確認する | assert 文で値・状態・例外などを検証 |
AAAパターンの基本形
先ほどのテストを AAAパターンで書き直すとこうなります。
# ✅ AAAパターンで書いた場合
def test_add_returns_correct_sum_with_positive_numbers():
# Arrange(準備): テストに必要なデータを用意する
a = 2
b = 3
# Act(実行): テスト対象の処理を1回実行する
result = add(a, b)
# Assert(検証): 結果が期待通りか確認する
assert result == 5
def test_add_returns_zero_when_adding_opposite_numbers():
# Arrange
a = -1
b = 1
# Act
result = add(a, b)
# Assert
assert result == 0💡 何が変わったか?
|
各フェーズの詳細と書き方
① Arrange(準備):テストの前提を作る
Arrange フェーズでは、テストに必要な変数・オブジェクト・データを用意します。このフェーズが長くなる場合は pytest fixture に切り出すと整理できます。
def test_user_full_name():
# Arrange: テストに必要なオブジェクトを生成する
user = User(first_name="田中", last_name="太郎")
# Act
result = user.get_full_name()
# Assert
assert result == "田中 太郎"⚠️ Arrange が長くなりすぎるときのサイン Arrange が10行を超えるようなら、テストが複雑すぎるか、fixture に切り出すことを検討しましょう。準備が長いとテストの本質(何を確認したいか)が埋もれてしまいます。 |
💡 AAAパターンと fixture の関係 fixture は AAA を置き換えるものではなく、Arrange 部分を複数テストで共通化するための仕組みです。
|
② Act(実行):検証したい処理を実行する
Act フェーズでは、検証したい処理を実行します。理想的には「1つの目的に絞った操作」ですが、実務ではログイン後に操作する・カートに追加してから合計を取得するなど、複数の手順が必要なケースも普通にあります。その場合でも「このテストで何を検証したいのか」が明確になるよう意識しましょう。
# ⚠️ Arrange と Act が混在している(構造がわかりにくい)
def test_bad_example():
cart = ShoppingCart()
cart.add_item("apple", price=100)
cart.add_item("banana", price=150)
cart.apply_discount(10) # ← 割引もテストしたいのか?合計だけ?
result = cart.total()
assert result == 225
# ✅ 何を検証するかを明確にした書き方
def test_total_is_correct_after_adding_two_items():
# Arrange(前提条件:カートに2商品が入っている)
cart = ShoppingCart()
cart.add_item("apple", price=100)
cart.add_item("banana", price=150)
# Act(検証対象:合計金額の取得)
result = cart.total()
# Assert
assert result == 250③ Assert(検証):期待値と実際の結果を比較する
Assert フェーズでは assert 文で結果を検証します。1テスト関数につき assert は1〜3個程度が目安です。pytest は assert 文の失敗時に詳細な差分を自動で表示してくれるため、複雑なアサーションライブラリがなくてもシンプルに書くことができます。
# ✅ シンプルな assert(推奨)
assert result == 5
assert result is not None
assert "error" not in response_body
assert status_code in (200, 201)
# ✅ エラーメッセージ付き(失敗時に原因がわかりやすい)
assert result == 5, f"期待値: 5, 実際の値: {result}"
# ❌ 1つの関数に assert を詰め込みすぎ
def test_user():
user = create_user("田中", "taro@example.com")
assert user.name == "田中"
assert user.email == "taro@example.com"
assert user.is_active is True
assert user.role == "user"
assert user.created_at is not None # これだけで5つの確認💡 複数の assert が必要なときは? ユーザー生成後の「名前・メール・権限・状態」をまとめて確認したい場合は、それぞれを別のテスト関数に分けるか、関連性が強い場合は2〜3個まとめるのが現実的です。「失敗したときに原因が1つに絞れるか」を基準に判断しましょう。 |
読みやすいテスト関数名の付け方
AAAパターンと同じくらい重要なのが、テスト関数名です。関数名を読むだけで「何をテストしているか」がわかると、失敗時の調査が格段に速くなります。
推奨パターン:test_[何を]_[どんな条件で]_[何が起きるか]
| 悪い例 | 良い例 |
|---|---|
test_login() | test_login_with_valid_credentials_returns_200() |
test_add() | test_add_returns_correct_sum_with_positive_numbers() |
test_error() | test_login_with_wrong_password_raises_auth_error() |
test_user_save() | test_save_user_with_duplicate_email_raises_value_error() |
長い名前に見えるかもしれませんが、テスト結果一覧に並んだとき、関数名だけで「何が失敗したか」が即座にわかります。ログを読む時間が大幅に短縮されます。
💡 複数の条件をテストしたい場合は parametrize が便利 正常系・境界値・異常系など複数のケースを AAAパターンで書くと関数が増えますが、 詳しくは pytest 完全ガイド を参照してください。 |
実践:よくあるテストを AAAパターンで書く
例①:計算関数のテスト
def add(a: float, b: float) -> float:
return a + b
def test_add_returns_correct_sum_with_positive_numbers():
# Arrange
a = 3
b = 4
# Act
result = add(a, b)
# Assert
assert result == 7
def test_add_returns_negative_when_both_negative():
# Arrange
a = -3
b = -4
# Act
result = add(a, b)
# Assert
assert result == -7例②:例外が発生することをテストする
import pytest
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("0で割ることはできません")
return a / b
def test_divide_raises_value_error_when_divisor_is_zero():
# Arrange
a = 10
b = 0
# Act & Assert(例外テストは Act と Assert が一体になる)
with pytest.raises(ValueError, match="0で割ることはできません"):
divide(a, b)例③:クラスのメソッドをテストする
class ShoppingCart:
def __init__(self) -> None:
self._items: list[dict] = []
def add_item(self, name: str, price: int) -> None:
self._items.append({"name": name, "price": price})
def total(self) -> int:
return sum(item["price"] for item in self._items)
def test_total_returns_sum_of_all_item_prices():
# Arrange
cart = ShoppingCart()
cart.add_item("apple", price=100)
cart.add_item("banana", price=200)
# Act
result = cart.total()
# Assert
assert result == 300
def test_total_returns_zero_when_cart_is_empty():
# Arrange
cart = ShoppingCart()
# Act
result = cart.total()
# Assert
assert result == 0AAAパターンで陥りやすいはまりポイント
🚧 はまりポイント一覧 ① Act フェーズに複数の操作を書いてしまう Act には「テストしたい処理」を1つだけ書きます。複数の操作を書くと、どの操作が原因で失敗したか特定しにくくなります。準備の操作は Arrange に移すのが正解です。 ② Arrange の中に assert を書いてしまう 「準備段階でも確認したい」という気持ちはわかりますが、Arrange 内の assert はテストの本題と混同されます。確認は必ず Assert フェーズに集約しましょう。 ③ テスト関数名が test_1、test_func など意味のない名前になる CI/CD でテストが失敗したとき、関数名が唯一の手がかりです。 ④ 1つのテストで複数の条件をまとめてしまう 「正常系・異常系・境界値をまとめて1つのテストで確認したい」という気持ちは理解できますが、それぞれ別のテスト関数に分けることで、どの条件が失敗したかが明確になります。 ⑤ コメント(# Arrange など)を省略する 慣れてくるとコメントを省略したくなりますが、チーム開発ではコメントがあることで他のメンバーが構造を素早く把握できます。少なくともテストに慣れるまでは省略しない方が安全です。 |
👨💻 実務のコードレビューでは何を見るか 筆者がテストコードをレビューするとき、最初に確認するのは AAAの構造になっているか です。「Arrange がなく Act と Assert だけのコード」や「Arrange に assert が混在しているコード」は、意図が読み取れず後のメンテナンスで混乱しやすいため、まず構造の修正を依頼します。テストコードは「動けばよい」だけでなく、チームの共有ドキュメントとして書くという意識が長期的な品質につながります。 |
よくある質問(FAQ)
必須ではありませんが、チーム開発・長期的な保守を考えると採用するメリットが大きいです。小規模・個人のスクリプト的なテストでは省略するケースもあります。まずは意識してみることが大切です。
問題ありません。pytest.raises() を使った例外テストは Act と Assert が一体化するのが自然です。このような場合は # Act & Assert とコメントを書くと意図が伝わります。
厳格なルールはありませんが、1〜3個程度が目安です。関連性の強い検証(例:レスポンスのステータスコードとボディを両方確認する)はまとめることがあります。ただし assert が増えるほど「どれが失敗原因か」を見つけにくくなります。
概念はほぼ同じです。Given-When-Then は BDD(振る舞い駆動開発)で使われる表現で、より自然言語に近く書けます。どちらを使っても構造は同じです。
| AAAパターン | Given-When-Then(BDD) | 意味 |
|---|---|---|
| Arrange | Given | 〜という状態のとき |
| Act | When | 〜を操作すると |
| Assert | Then | 〜という結果になる |
TDD(テスト駆動開発)は「テストを先に書いてからコードを実装する開発手法」です。AAAパターンはテストの書き方の構造であり、TDDと組み合わせて使えます。TDD でテストを書く際も AAAパターンで構造化すると可読性が上がります。
使えます。UI テストでは Arrange にドライバー起動・ログイン処理などを書き、Act にテストしたい画面操作(ボタンクリック・フォーム入力など)を書き、Assert にページのタイトルや表示内容の確認を書くのが典型的なパターンです。
できます。parametrize はテストデータを外側から渡す仕組みです。@pytest.mark.parametrize でデータを与えつつ、テスト関数内を AAA 構造にすることで「複数条件を整理しながら読みやすいコード」が書けます。複数のテストケースをまとめる際の最初の選択肢として覚えておきましょう。
はい。テスト間で共通する準備処理(データベース接続・オブジェクト生成など)を fixture として切り出すと Arrange フェーズが短くなり、テストの本質が見やすくなります。fixture は Arrange の「より整理された形」と考えてください。詳しくは pytest 完全ガイド を参照してください。
まとめ
|
AAAパターンは「難しいテクニック」ではなく、テストコードを読む人への配慮です。将来の自分やチームメンバーが「何をテストしているか」を一目で理解できるコードを書くことが、保守しやすいテストスイートへの第一歩です。まずは次に書くテスト関数から、AAAパターンを意識してみてください。
📚 あわせて読みたい関連記事 |
