第22章:単体テスト①(遷移表ベースで作る)🧪✨
(テーマ:**「遷移表 = テストケースの原材料」**にしちゃう💡)
1) この章のゴール🎯💖
この章が終わったら、こんなことができるようになります👇✨
- 遷移表の1行を、そのままテスト1件に落とせる 🧾➡️🧪
- 正常遷移テスト / 禁止遷移テスト を、まとめて量産できる🔁✨
- テストが増えても破綻しない 「データ駆動(Theory)」 の書き方がわかる📦✨
(
dotnet testは VSTest / Microsoft Testing Platform どちらにも対応、という最新事情も踏まえます🧠✨) (Microsoft Learn)
2) まず結論:遷移表って、そのままテスト仕様書だよ📊💎

遷移表ってこういうやつ👇(例)
| 現在状態 | イベント | 次状態 | できる? | 失敗理由コード |
|---|---|---|---|---|
| Draft | Submit | Submitted | ✅ | - |
| Submitted | Pay | Paid | ✅ | - |
| Paid | Cancel | (変化なし) | ❌ | CannotCancelAfterPaid |
この1行 = 1テストケースにしちゃうのが今回の主役です🧪✨
- ✅行 → 「次状態が合ってる?」をテスト
- ❌行 → 「失敗になる?理由コード合ってる?」をテスト
こうすると、状態が増えても「表を足す → テストも増える」が自然に回るよ〜🥰
3) 今回のテスト方針(超シンプル版)🍙✨
この章では、まず最小セットでいきます🧸
A. 正常遷移テスト✅
- Given:初期状態
- When:イベント適用
- Then:成功して、期待の次状態になる
B. 禁止遷移テスト🚫
- Given:初期状態
- When:イベント適用
- Then:失敗して、状態は変わらない(or 仕様通り)+理由コードが合う
4) テストしやすい「状態機械の返り値」形を決めよう📦✨
テストがラクになる“推し”の形はこれ👇
Apply(event)が 例外を投げず(※禁止遷移は“失敗として返す”)TransitionResultに「成功/失敗」「次状態」「理由コード」が入ってる
例(雰囲気)👇
public enum OrderState
{
Draft, Submitted, Paid, Cooking, Ready, PickedUp, Cancelled, Refunded
}
public interface IOrderEvent { }
public sealed record Submit() : IOrderEvent;
public sealed record Pay(decimal Amount) : IOrderEvent;
public sealed record Cancel(string Reason) : IOrderEvent;
public sealed record TransitionResult(
bool IsSuccess,
OrderState State,
string? ErrorCode = null,
string? ErrorMessage = null
);
public sealed class OrderStateMachine
{
public OrderState State { get; private set; } = OrderState.Draft;
public TransitionResult Apply(IOrderEvent ev)
{
// ここは第15〜19章で作った実装がある想定でOK👍
// 今回は「テスト側」を主役にするよ🧪✨
throw new NotImplementedException();
}
}
「禁止遷移で例外」より「Resultで返す」の方が、UI/APIに繋げる時もテストもラクになりがちです☺️✨(第19章の流れとも相性◎)
5) テストプロジェクト作成&実行(最短)🏃♀️💨
CLIで作るならこれだけ👇(xUnit テンプレあり✨) (Microsoft Learn)
dotnet new sln -n CafeteriaOrder
dotnet new classlib -n CafeteriaOrder.Core
dotnet new xunit -n CafeteriaOrder.Tests
dotnet sln CafeteriaOrder.sln add CafeteriaOrder.Core/CafeteriaOrder.Core.csproj
dotnet sln CafeteriaOrder.sln add CafeteriaOrder.Tests/CafeteriaOrder.Tests.csproj
dotnet add CafeteriaOrder.Tests/CafeteriaOrder.Tests.csproj reference CafeteriaOrder.Core/CafeteriaOrder.Core.csproj
dotnet test
dotnet testは最新の仕組み(VSTest / MTP)も含めて進化してるので、困ったら公式の挙動説明を見るのが早いです🧠✨ (Microsoft Learn)- Visual Studioなら Test Explorer でポチ実行もOK👌(右クリックでOpen Testとかもできるよ) (Microsoft Learn)
6) 遷移表を「テストデータ」にする(ここが本題)📊➡️🧪✨
6.1 テストケース用の型を作る🧾✨
public sealed record TransitionCase(
OrderState Given,
IOrderEvent When,
bool ExpectSuccess,
OrderState ExpectState,
string? ExpectErrorCode = null
);
6.2 遷移表(の一部)を “そのまま” データ化📚✨
xUnitでは Theory + MemberData(データ駆動)が相性抜群です💖 Theoryは「同じテストを入力違いで回す」機能だよ〜🧠✨ (Microsoft Learn)
using System.Collections.Generic;
public static class OrderTransitionTable
{
public static IEnumerable<object[]> Cases()
{
// ✅ 正常遷移
yield return new object[] { new TransitionCase(OrderState.Draft, new Submit(), true, OrderState.Submitted) };
yield return new object[] { new TransitionCase(OrderState.Submitted, new Pay(500m), true, OrderState.Paid) };
// 🚫 禁止遷移(例)
yield return new object[] { new TransitionCase(OrderState.Paid, new Submit(), false, OrderState.Paid, "CannotSubmitAfterPaid") };
yield return new object[] { new TransitionCase(OrderState.Paid, new Cancel("no"), false, OrderState.Paid, "CannotCancelAfterPaid") };
}
}
※
MemberDataは static が要求されがちなので、こういうstatic classに寄せると事故りにくいです🙏✨
- 遷移表が更新されたら、テストも一緒に更新できる(仕様と同期しやすい)📘✨
7) たった1本のテストで「表の行全部」を検証する🧪🔁✨
using Xunit;
public sealed class OrderStateMachine_TransitionTests
{
[Theory]
[MemberData(nameof(TransitionCases))]
public void Transition_table_driven_test(TransitionCase tc)
{
// Arrange
var sm = new OrderStateMachine();
SetStateForTest(sm, tc.Given);
// Act
var result = sm.Apply(tc.When);
// Assert
Assert.Equal(tc.ExpectSuccess, result.IsSuccess);
Assert.Equal(tc.ExpectState, result.State);
if (!tc.ExpectSuccess)
{
Assert.Equal(tc.ExpectErrorCode, result.ErrorCode);
}
}
public static IEnumerable<object[]> TransitionCases() => OrderTransitionTable.Cases();
private static void SetStateForTest(OrderStateMachine sm, OrderState state)
{
// 本当は「状態を外からいじらない」方がキレイ💅
// でも学習の最初は、テストのために“注入”できる仕組みを用意するのがコツだよ✨
// 例:コンストラクタで初期状態を受け取れるようにする / internal setter + InternalsVisibleTo など
throw new NotImplementedException();
}
}
✅この形の強いところ💪✨
- テストが増えても メソッド1本のまま
- ケース追加は データを1行足すだけ
- 遷移表が更新されたら、テストも一緒に更新できる(仕様と同期しやすい)📘✨
8) 演習:まず “10ケース” 作ろう🔟🧪✨
次の方針で10件作ると、実戦っぽくなります💖
8.1 正常遷移(6件くらい)✅
- Draft → Submit → Submitted
- Submitted → Pay → Paid
- Paid → (調理開始イベント)→ Cooking
- Cooking → (調理完了イベント)→ Ready
- Ready → PickUp → PickedUp
- Submitted → Cancel → Cancelled
8.2 禁止遷移(4件くらい)🚫
- Draft で Pay(まだ注文してない)→ ❌
CannotPayBeforeSubmit - Paid で Cancel → ❌
CannotCancelAfterPaid - PickedUp で Cancel → ❌
CannotCancelAfterPickedUp - Cancelled で PickUp → ❌
CannotPickUpAfterCancelled
ここで大事なのは「それっぽいコード名」を決めて、未来の自分が読んで分かるようにすることだよ☺️🧾✨
9) AI活用(この章はめちゃ相性いい)🤖💖
AIは 「表→テストデータ」変換が得意です🎯✨
9.1 おすすめプロンプト(そのままコピペOK)📋✨
- 「次の遷移表を、xUnit の
IEnumerable<object[]>でTransitionCaseの列にしてください。失敗ケースはErrorCodeも埋めてください。C#コードだけで出力して。」 - 「禁止遷移の理由コード(ErrorCode)を、命名規則
Cannot{Event}When{State}で提案して。」 - 「この遷移表から、優先度の高い10ケースだけ選んで(ユーザー影響が大きい順)、テストデータを作って。」
9.2 注意点(AIあるある)⚠️😂
- 状態名やイベント名を勝手に増やすことがある → 表の用語に合わせてね✋
- 失敗コードがブレる → 命名規則を先に決めるのが勝ち🏆✨
10) つまずきポイント集(先回り)🧯✨
10.1 Visual Studioでテストが出てこない😵💫
だいたいは テストアダプタ まわりが原因になりがち。
xUnit では xunit.runner.visualstudio が “VSTest(Test Explorer)用アダプタ” です🧩✨ (NuGet)
(※最近は xUnit v3 + Microsoft Testing Platform みたいな新しい流れもあるので、パッケージの整合性は大事だよ〜🧠✨ (xunit.net))
10.2 Theoryのデータが不安定で、表示や実行が変になる🌀
DateTime.Now みたいな「毎回変わる値」をテストデータに入れると、Test Explorerの扱いがややこしくなることがあります💦
xUnit側も “Theoryデータの安定性” を注意喚起してるよ🧠 (xunit.net)
10.3 dotnet test のモード混在で困る🧩
ソリューション内で VSTest と MTP を混ぜるのは注意、という公式ガイドもあります⚠️ (Microsoft Learn) (この講座では、まずは “同じ流儀で統一” が安全だよ☺️)
11) まとめ🍙✨(次章へのつながりも)
この章でやったことはこれ👇🧪💖
- ✅ 遷移表の1行 = テスト1件 を体験した
- ✅ Theory/MemberDataで 表からテストを量産できた (Microsoft Learn)
- ✅ 禁止遷移も「失敗として返す」なら、テストがきれいに書ける✨
次の第23章では、ここからさらに一段進めて 「全部はテストできない…じゃあ何から?」 を優先度づけしていきます🎯🧪✨