状態遷移テストとは、システムの「状態」と「状態を変化させるイベント」を整理して、遷移が正しく動作するかを検証するテスト設計技法です。ログインフロー・ECカート・予約システムなど、状態を持つシステムのテストに特に効果を発揮します。
状態遷移テストを使うと「正常な遷移が正しく動くか」だけでなく「あってはならない遷移が正しく拒否されるか」まで体系的にテストでき、フロー系バグを根こそぎ防ぐことができます。
📌 この記事はこんな方におすすめ
- 状態遷移テストを初めて学ぶQAエンジニア・開発者
- ログイン・カート・注文フローのテストを体系化したい方
- Playwright E2Eテストのシナリオ設計に役立てたい方
- JSTQBで状態遷移テストを理解したい方
✅ この記事を読むと得られること
- 状態遷移テストの概念・状態遷移図・状態遷移表の作り方がわかる
- ログインフロー・ECカートへの具体的な適用方法がわかる
- Playwright × pytestでのE2Eテストへの応用方法がわかる
👤 この記事を書いた人
QAエンジニア・テスト自動化エンジニアとして15年以上の実務経験を持つ Yoshi が執筆。状態遷移テストはECサイト・予約システム・認証フローなど状態を持つシステムで毎日活用している技法です。実装コードはGitHubで公開中:github.com/YOSHITSUGU728/automated-testing-portfolio
「ログアウト状態で注文確定ボタンを押したらどうなる?」「キャンセル済みの注文を再度キャンセルできてしまう?」——こうした「あってはならない操作」のテストを見落としていませんか?
- 正常フローしかテストしておらず、異常な状態遷移が見落とされる
- どの状態からどの操作が可能かが整理できていない
- フローが複雑になるほどテストケースが発散してしまう
状態遷移テストはこうした問題を解決する技法です。この記事では概念・状態遷移図・状態遷移表の作り方から、Playwright E2Eテストへの応用まで一気通貫で解説します。
📌 結論
- 状態・イベント・遷移先・アクションを整理したテスト設計技法で、正常遷移と無効遷移の両方をテストする
- 状態遷移図で全体像を視覚化し、状態遷移表でテストケースを網羅的に導き出す
- Playwright E2Eテストと相性が抜群で、各状態をfixture・各遷移をテスト関数として実装できる
状態遷移テストとは?
状態遷移テストとは、「システムが持つ状態」「状態を変化させるイベント(操作)」「遷移後の状態」「実行されるアクション」を整理して、すべての遷移が正しく動作するかを検証するテスト設計技法です。
例えばECサイトの注文フローでは「カート未追加」→「カート追加済み」→「注文確定」→「発送済み」→「受取完了」という状態の変化が発生します。状態遷移テストはこの流れを図や表で整理し、すべてのパターンを漏れなくテストします。
| 比較項目 | 状態遷移テストなし | 状態遷移テストあり |
|---|---|---|
| テスト範囲 | 正常フローのみになりがち | 正常・異常遷移を網羅 |
| 無効操作の検証 | 見落としが多い | 表で体系的に確認できる |
| チームへの共有 | 口頭・文書で説明が必要 | 図・表で直感的に共有できる |
| E2Eテストへの変換 | シナリオが場当たり的になる | 遷移ごとにテスト関数を設計できる |
状態遷移テストの基本要素とは?
状態遷移テストを設計するには、次の4つの要素を整理します。
| 要素 | 説明 | 例 |
|---|---|---|
| 状態(State) | システムが今どの状態にあるか | ログアウト中・ログイン中・注文確定 |
| イベント(Event) | 状態を変化させる操作・入力 | ログインボタン押下・注文確定ボタン |
| 遷移(Transition) | 状態からイベントによって次の状態へ変わること | ログアウト中 → ログイン中 |
| アクション(Action) | 遷移時に実行される処理 | セッション発行・確認メール送信 |
状態遷移図(State Diagram)の作り方とは?
状態遷移図は、状態を丸(○)・遷移を矢印(→)・イベントをラベルで表した図です。全体の流れを直感的に把握できます。
※ 状態は「丸」、遷移は「矢印」、イベントは「ラベル」で表現します。図を初めて読む場合は、まず丸(状態)を確認し、矢印(遷移)をたどってみましょう。
📊 ログインフローの状態遷移図
ログアウト中 | → ログイン成功 | ログイン中 | → 注文確定 | 注文確定 |
| ↑ ログアウト (自分自身へ戻る) | ↓ ログアウト (ログアウト中へ) | ↓ キャンセル申請 (キャンセル済みへ) |
※ 丸=状態 矢印=遷移 ラベル=イベント
状態遷移図のメリット
- 全体の流れを一目で把握できる:状態と遷移の関係が視覚的にわかる
- チームへの説明が楽になる:開発・QA・ビジネス側で共通認識を持てる
- 見落としの発見に役立つ:図を書く過程で「この状態への遷移がないぞ?」という気づきが生まれる
状態遷移表(State Transition Table)の作り方とは?
状態遷移表は、状態遷移図を表形式に変換したものです。各状態×各イベントの組み合わせで「遷移先」または「無効(—)」を記入することで、テストケースを網羅的に洗い出せます。
実例:ログインフローの状態遷移表
| 現在の状態 | ログイン成功 | ログイン失敗 | ログアウト | 注文確定 | キャンセル |
|---|---|---|---|---|---|
| ログアウト中 | ログイン中 | ログアウト中 (エラー表示) | — | — | — |
| ログイン中 | — | — | ログアウト中 | 注文確定 | — |
| 注文確定 | — | — | — | — | キャンセル済み |
| キャンセル済み | — | — | — | — | — (無効・エラー) |
表中の「—」はそのイベントが現在の状態では無効(発生してはいけない、または発生しても状態が変わらない)ことを示します。この「—」の部分こそが「無効遷移テスト」のテストケースになります。
💡 重要:表中の「—(無効遷移)」はすべてテスト対象です。
無効遷移のテストを省略すると「キャンセル済みの注文を再度キャンセルできてしまう」「ログアウト状態でAPIを直叩きしたら注文が通ってしまった」といったバグを見落とします。正常遷移だけでなく「—」セルを必ずテストする習慣をつけましょう。
実例:ECサイトの注文フロー
より実務に近い例として、ECサイトの注文フローをテストケースに落とし込みます。
| テストケース | 現在の状態 | イベント | 期待される遷移先 | 確認ポイント |
|---|---|---|---|---|
| TC-01 ✅ | カート未追加 | 商品を追加 | カート追加済み | カートバッジ数が増えるか |
| TC-02 ✅ | カート追加済み | 購入を確定 | 注文確定 | 確認メールが送信されるか |
| TC-03 ✅ | 注文確定 | キャンセル申請 | キャンセル済み | 在庫が戻るか・返金処理が走るか |
| TC-04 ❌ | カート未追加 | 購入を確定(無効) | カート未追加(変化なし) | エラーまたはボタンが無効化されているか |
| TC-05 ❌ | キャンセル済み | 再度キャンセル(無効) | キャンセル済み(変化なし) | エラーまたは操作できない状態か |
| TC-06 ❌ | 発送済み | キャンセル申請(無効) | 発送済み(変化なし) | キャンセルボタンが非表示か・エラーか |
✅は正常遷移テスト(正しい操作が正しく動く)、❌は無効遷移テスト(無効な操作が正しく拒否される)を示しています。両方をカバーすることで、フロー全体の品質を担保できます。
Playwright × pytestへの応用
状態遷移テストはPlaywright × pytestのE2Eテストと非常に相性がよく、「各状態をfixture・各遷移をテスト関数」として実装するのが実務のベストプラクティスです。
フォルダ構成の例
tests/
├── conftest.py # fixtureの定義(状態の準備)
└── e2e/
└── test_order_flow.py # 注文フローの状態遷移テストconftest.py:状態をfixtureで準備する
import pytest
from playwright.sync_api import Page
@pytest.fixture
def logged_in_page(page: Page):
"""状態:ログイン中(ログイン済みのページを返す)"""
page.goto("localhost:3000/login")
page.fill("#username", "test_user")
page.fill("#password", "secret_pass")
page.click("#login-btn")
page.wait_for_url("**/dashboard")
return page
@pytest.fixture
def cart_added_page(logged_in_page: Page):
"""状態:カート追加済み(ログイン済み+商品追加済み)"""
logged_in_page.goto("localhost:3000/products/1")
logged_in_page.click("#add-to-cart")
logged_in_page.wait_for_selector(".cart-badge")
return logged_in_pagetest_order_flow.py:遷移をテスト関数として実装する
from playwright.sync_api import Page, expect
# TC-01:カート未追加 → 商品追加 → カート追加済み
def test_add_item_to_cart(logged_in_page: Page):
logged_in_page.goto("localhost:3000/products/1")
logged_in_page.click("#add-to-cart")
expect(logged_in_page.locator(".cart-badge")).to_have_text("1")
# TC-02:カート追加済み → 購入確定 → 注文確定
def test_confirm_order(cart_added_page: Page):
cart_added_page.goto("localhost:3000/cart")
cart_added_page.click("#checkout-btn")
expect(cart_added_page.locator(".order-status")).to_have_text("注文確定")
# TC-04:カート未追加 → 購入確定(無効遷移)
def test_checkout_without_cart(logged_in_page: Page):
logged_in_page.goto("localhost:3000/cart")
# カートが空のときは購入ボタンが無効化されているか確認
expect(logged_in_page.locator("#checkout-btn")).to_be_disabled()
# TC-05:キャンセル済み → 再キャンセル(無効遷移)
def test_cancel_already_cancelled_order(logged_in_page: Page):
logged_in_page.goto("localhost:3000/orders/cancelled-order")
# キャンセル済み注文にはキャンセルボタンが存在しないことを確認
expect(logged_in_page.locator("#cancel-btn")).not_to_be_visible()⚠️ 状態遷移テストでよくある失敗パターン4選
実務で状態遷移テストを設計するときに陥りやすい失敗を紹介します。
① 正常遷移だけテストして無効遷移を見落とす
「正常なフローで正しく動くこと」のテストだけで満足してしまうのが最もよくある失敗です。「キャンセル済みの注文を再度キャンセルできてしまう」「ログアウト状態でAPIを直叩きしたら注文が通ってしまった」というバグはすべて無効遷移の見落としです。状態遷移表の「—」セルを必ずテストする習慣をつけましょう。
② 状態の定義が曖昧なままテストを始める
「ログイン中」と「セッション期限切れ」は別の状態です。「注文確定」と「支払い完了」も異なる場合があります。状態の境界が曖昧なままテストを始めると、カバーできていない状態が生まれます。仕様書を確認しながら状態を明確に定義してから設計を始めましょう。
③ 状態遷移図だけ作って状態遷移表を省略する
状態遷移図は「全体の流れ」を把握するのに便利ですが、それだけではテストケースの網羅性を担保できません。状態遷移表に落とし込むことで、「この状態×このイベントの組み合わせをテストしたか」が一目でわかるようになります。図と表の両方を作るのがプロの設計です。
④ E2Eテストで状態の準備をテスト関数の中に書いてしまう
「ログイン→商品追加→購入確定→キャンセル」の一連の操作をすべて1つのテスト関数に書いてしまうケースです。こうすると前の遷移が失敗すると後のテストが実行できなくなります。pytestのfixtureで「状態を準備する処理」を分離することで、各テストが独立して実行できるようになります。
FAQ
Q. 状態遷移テストはどんなシステムに使うべきですか?
「状態」を持つシステム全般に有効です。ECサイト(カート・注文・支払い・配送)・予約システム(仮予約・確定・キャンセル)・認証フロー(ログアウト・ログイン・セッション期限切れ)などが代表例です。逆に状態を持たないシンプルな入力フォームや計算処理には、同値分割や境界値分析の方が適しています。
Q. 状態遷移テストとユースケーステストの違いは?
状態遷移テストは「状態とその遷移が正しいか」に注目します。無効遷移のテストも含みます。ユースケーステストは「ユーザーのゴールへの一連の操作フロー」をテストします。実務では両方を組み合わせることが多く、ユースケーステストで正常フローを検証しつつ、状態遷移テストで無効遷移を補完するという構成が一般的です。
Q. JSTQBで状態遷移テストはどの程度理解すれば合格できますか?
JSTQB Foundation Levelでは「状態遷移図・状態遷移表の読み方と基本的なテストケースの導き方」が問われます。この記事のログインフローの例(状態遷移図・状態遷移表・正常遷移と無効遷移の区別)レベルが理解できていれば合格ラインです。「0スイッチカバレッジ(全状態)」「1スイッチカバレッジ(全遷移)」の用語も押さえておきましょう。
Q. 状態が多くなりすぎた場合はどうすればいいですか?
状態が10個を超えてくると状態遷移表が巨大になりすぎます。その場合は「サブシステム単位でテーブルを分割する」「優先度の高い遷移(頻度が高い・リスクが高い)から先にテストする」という方法が有効です。全パターンをカバーするよりも、リスクベースで重要な遷移に集中する判断も実務では重要です。
実務では、状態遷移図をスプレッドシートまたは draw.io などのツールで作成し、レビュー後にPlaywright × pytestのE2Eテストに落とし込むワークフローがよく使われます。fixtureで「状態」を、テスト関数で「遷移」を表現することで、設計書とコードが1対1で対応する保守性の高いテストスイートが完成します。
📋 この記事のまとめ
- 状態遷移テストは「正常遷移」と「無効遷移」の両方をテストするテスト設計技法
- 状態遷移図で全体の流れを視覚化し、状態遷移表でテストケースを網羅的に洗い出す
- 表中の「—」(無効遷移)こそがバグの温床。必ずテストする
- Playwright × pytestではfixtureで状態を・テスト関数で遷移をそれぞれ実装するのがベストプラクティス
- 状態が多すぎる場合はサブシステム分割・リスクベーステストで優先度をつける
状態遷移テストの最大の価値は「正常フローだけではわからないバグ」を体系的に発見できる点です。まずはログインフローや注文フローなど身近なシステムの状態遷移図を1枚書いてみることから始めましょう。図を書くだけで「この遷移のテスト、やってなかった!」という気づきが必ず生まれます。
