第34章 テスト設計②:Adapterのテストは別物🔍🧪
第33章で「Core(ユースケース)」の単体テストがスッと書ける快感を味わったよね?😆💖 でも第34章はちょい別ジャンル!Adapterのテストは“外の世界”が相手になるから、目的もやり方も変わるよ〜🧭✨
🎯この章のゴール
- CoreテストとAdapterテストの目的の違いをスパッと分けられる🙂✂️
- DB/HTTPなどOutbound Adapterを“現実的な範囲”でテストできる🧪🗄️
- Inbound Adapter(API/Controller)の統合テストの基本がわかる🌐✅
- 「どこまでやる?」の現実ラインが持てる📌😌
🧠 まず結論:CoreのテストとAdapterのテストは“守るもの”が違う
✅ Core(UseCase)のテストで守るもの
- 業務ルール(計算・状態遷移・バリデーションの方針)🧠💎
- 依存はFake/Stubで差し替え🧩✨ → 速い!安い!大量に回せる!🏃♀️💨
✅ Adapterのテストで守るもの
-
「外部とちゃんと噛み合ってるか」🔌🤝
-
例:
- DB:SQL/ORM設定、マッピング、制約、トランザクション🗄️
- HTTP:認証ヘッダ、URL、パラメータ、JSON形式、リトライ方針📡 → 遅い!環境が要る!でも壊れると痛い場所😇
🧱 テストの種類を整理(ここ超大事)🎯

Adapterのテストは、だいたいこの2つに寄るよ👇
- **統合テスト(Integration Test)**🧪
- 例:DBに本当に接続して、保存→取得が通るか
- 例:APIをテストサーバーで起動して、HTTPで叩く 統合テストは「複数コンポーネントが一緒に動くか」を確認するテストだよ〜📌 (ASP.NET Coreの統合テストの説明はこの感じ!)(Microsoft Learn)
- **契約テスト(Contract Test)**📜🤝
- 「このAdapterはこの形式で話す」を固定するテスト
- 外部仕様(DBスキーマ/HTTPレスポンス形式)が変わると爆発するので、ここで検知💥👀
🧪 どこまでやる?現実的ライン📌🙂
Adapterは全部をE2Eでやり始めると破産しがち💸😭 おすすめのラインはこれ👇
🌟 最低限やる(強い)
- マッピングが正しいか(Domain ↔ DBモデル、Domain ↔ DTO)🔁✅
- 重要なクエリが正しいか(検索条件、ソート、ページング)🔎🗄️
- 失敗時の扱い(例:接続失敗→例外の種類、再試行する/しない)🧯⚡
💤 やりすぎ注意(コスパ悪化しがち)
- 画面UIの細かい見た目テスト🎨😵💫
- 1テストで全部の経路を通す“巨大テスト”🦑💥
🗄️ Outbound:DB Adapterのテスト(統合テスト寄り)🧪🗃️
DB Adapterは「本物のDBで動かす」方が信頼度高いよ💪 最近は Testcontainers がめちゃ便利(テスト時だけDBをコンテナで起動)🐳✨ (.NET用のモジュールとして Postgres などが用意されてるよ)(dotnet.testcontainers.org)
✅ パターンA:Testcontainers(おすすめ)🐳
狙い:ローカル/CIでも同じ条件でDBが立つ → “動く保証”が強い✨
ざっくり流れ
- テスト開始 → DBコンテナ起動🐳
- 接続文字列をAdapterに渡す🔌
- マイグレーション実行(必要なら)🛠️
- 保存→取得→検証🧪
- テスト終了 → コンテナ破棄🧹
xUnit例(雰囲気サンプル)
(xUnitは v3 が .NET 8+ 対応って書かれてるよ)(xUnit.net)
using Xunit;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
public sealed class OrderRepository_IntegrationTests : IAsyncLifetime
{
private IContainer _db = default!;
private string _connectionString = default!;
public async Task InitializeAsync()
{
// 例:PostgreSQL。実務ではSQL Server等でもOK(同じ発想!)
_db = new ContainerBuilder()
.WithImage("postgres:16")
.WithEnvironment("POSTGRES_PASSWORD", "postgres")
.WithPortBinding(5432, true)
.Build();
await _db.StartAsync();
var hostPort = _db.GetMappedPublicPort(5432);
_connectionString = $"Host=localhost;Port={hostPort};Username=postgres;Password=postgres;Database=postgres";
// ここでマイグレーション/テーブル作成など
// await Migrator.ApplyAsync(_connectionString);
}
public async Task DisposeAsync()
{
await _db.DisposeAsync();
}
[Fact]
public async Task Save_Then_Load_Returns_Same_Order()
{
// Arrange
var repo = new OrderRepository(_connectionString);
var order = TestData.NewOrder();
// Act
await repo.SaveAsync(order);
var loaded = await repo.FindByIdAsync(order.Id);
// Assert
Assert.NotNull(loaded);
Assert.Equal(order.Id, loaded!.Id);
Assert.Equal(order.TotalPrice, loaded.TotalPrice);
}
}
ポイント💡
- このテストは「業務ルール」じゃなくて DBとの接続・マッピング・SQLの正しさが目的🎯
- Core側のテストで同じことをやり直さない!(二重投資を避ける)🙅♀️💸
✅ パターンB:SQLite in-memory(軽いけど注意)🪶🗄️
速いけど、DBごとの差(型、ロック、SQL方言、制約)が出ることがあるよ⚠️😵💫 「軽い安心」は取れるけど、本番DBとの差で事故る可能性は残る…!
🌐 Inbound:API/Controllerの統合テスト(入口のテスト)🚪🧪
Inbound Adapterのテストは「HTTPとして正しく受け取れるか」を見るやつだね🙂📮
ASP.NET Coreは WebApplicationFactory でテスト用サーバーを立てて叩けるよ✨ (WebApplicationFactoryの説明はここ)(Microsoft Learn)
using System.Net;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
public class OrdersApi_IntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public OrdersApi_IntegrationTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task POST_orders_returns_201()
{
var req = new { items = new[] { new { menuId = "coffee", qty = 1 } } };
var res = await _client.PostAsJsonAsync("/orders", req);
Assert.Equal(HttpStatusCode.Created, res.StatusCode);
}
}
ここで守るもの🎯
- ルーティング、HTTPステータス、JSON形式、バリデーションの入口の挙動✅
- 業務ルールの中身はCoreのテストに任せる(ここでも重複しない)🧠✂️
📡 Outbound:外部HTTP API Adapterのテスト(2段階が便利)🌍🧪
HTTP系は「全部本番APIに当てる」は危険だし遅い😇💥 なのでおすすめは2段階👇
① 速い:メッセージハンドラ差し替え(疑似レスポンス)⚡
- URL組み立て、ヘッダ付与、レスポンスのパース(DTO→Domain)を確認
- 失敗レスポンス時の例外変換もチェック🧯
② 強い:スタブサーバー(WireMock等)で“それっぽく”統合🧱
- 「このパスにこう投げたら、こう返る」を契約として固定📜
- CIでも安定しやすい😌✨
🏷️ 「遅いテスト」を普段の開発から守るコツ😆🛡️
統合テストは回す価値あるけど、毎回フルで回すとしんどい😵💫 なのでラベル付けして分けるのが定番!
[Trait("Category", "Integration")]
public class OrderRepository_IntegrationTests { /* ... */ }
- ふだん:Unitだけ(サクサク)🏃♀️💨
- PR前/CI:Integrationも含める(安心)🧪✅
🤖 AI活用のしかた(Adapterテストは特に相性いい)✨
AIに任せやすいのはここ👇
- テストの雛形生成(Arrange/Act/Assertの形)🧱
- ありがちなパターン(Testcontainers初期化、WebApplicationFactory)📦
でも人間が必ず見るのはここ👇🚦
- Port/Adapterの境界を破ってない?(Coreに外の都合を逆流させてない?)🧼
- 何を守るテストか(目的がブレて巨大化してない?)🎯
✅ 章末チェックリスト(ここだけ見返せばOK)📌✨
- ✅ Adapterテストは「外との噛み合わせ」を守ってる?🔌
- ✅ Coreの業務ルールをAdapterテストで二重に検証してない?🧠💦
- ✅ DB Adapterは最低限「マッピング」「重要クエリ」「失敗時」を押さえた?🗄️
- ✅ Inboundは「HTTP入口として正しいか」を見てる?🌐
- ✅ 統合テストはラベル分けして、回し方を分離した?🏷️
次の第35章は、仕上げとして「エラー・ログ・AI活用」を最小セットでまとめるよ〜🎁✨ テストで“壊れたらすぐ気づける”状態ができたら、運用もめっちゃ楽になる😆🛡️