第12章:インターフェースでI/Oを包む 🧩📮✨
ここから一気に「テストできる世界」に入っていくよ〜!😊🎉 第12章のテーマは超重要で、ひとことで言うと👇
**「外の世界(I/O)に触る部分は、インターフェース越しにする」**🚪🌍✨ そうすると、テストで “偽物” に差し替えできて、ロジックを安定して検証できるようになるよ〜!🧪💖
1) この章のゴール 🎯✨
できるようになってほしいことはこれ👇
- I/Oっぽい処理を見つけたら「インターフェースで包む」を自然に思い出せる 🧠💡
- インターフェースは “内側(ロジック側)” に置く のが基本だとわかる 📦✅
- 命名や粒度の “ちょうど良さ” を掴める 📝✨
- テストでは Fake/Stub で差し替えできる 🎭🧪
2) そもそも「I/Oを包む」ってどういうこと?🔌📦

たとえば、こういうの👇(全部 “外の世界” だよ〜)
- 時刻:
DateTime.Now🕰️ - ファイル:
File.ReadAllText🗂️ - ネット:HTTP呼び出し 🌐
- メール送信 ✉️
- DBアクセス 🗄️
こういう I/Oをロジックのど真ん中で直に呼ぶ と、テストが苦しくなるの😵💫💥 (遅い・落ちる・環境が必要・結果が揺れる…)
だから👇
✅ ロジックは「インターフェース」にだけ依存する
- 本番:インターフェースの 本物実装 を注入する 🔌
- テスト:インターフェースの 偽物実装 を注入する 🎭
この切り替えができるようになるのが、テスタブル設計の“中核”だよ💎✨
3) どこにインターフェースを書くのが正解?📍😊

ここ、めちゃ大事!!🚨
✅ 原則:インターフェースは「内側」に置く
- “内側” = ルール・ユースケース・大事なロジック 📦✨
- “外側” = I/Oの実装(ファイル、HTTP、DB、メール…)🌍🔌
つまり👇 「ロジックが欲しい形」を内側で決めて、外側が合わせに行く って感じ🧩✨
この考え方は、後で出てくるクリーン/ヘキサゴナルにもそのまま繋がるよ〜🌉💖
4) 最小サンプル メール送信を包む ✉️🧩
「メール送信」って、完全にI/Oだよね🌍 なので IEmailSender みたいに包むよ〜!
4-1) インターフェースを作る
public interface IEmailSender
{
Task SendAsync(string to, string subject, string body, CancellationToken ct = default);
}
ポイント👇
- “送る” という役割が見える名前でOK 📝✨
- 余計な機能を盛らない(最初は最小で!)🧼
4-2) ロジック側はインターフェースだけを見る
public sealed class WelcomeService
{
private readonly IEmailSender _emailSender;
public WelcomeService(IEmailSender emailSender)
=> _emailSender = emailSender;
public Task WelcomeAsync(string email, CancellationToken ct = default)
{
var subject = "ようこそ!";
var body = "登録ありがとう〜!🎉";
return _emailSender.SendAsync(email, subject, body, ct);
}
}
ここが最高に大事👇
- ロジック側に「SMTP」とか「API」とか一切出てこない🧠✨
- テストでは
_emailSenderを偽物にできる🎭🧪
5) テストでFakeを差し替える 🎭🧪✨
5-1) Fake実装を作る
public sealed class FakeEmailSender : IEmailSender
{
public List<(string To, string Subject, string Body)> Sent { get; } = new();
public Task SendAsync(string to, string subject, string body, CancellationToken ct = default)
{
Sent.Add((to, subject, body));
return Task.CompletedTask;
}
}
5-2) xUnitでテストする
using Xunit;
public class WelcomeServiceTests
{
[Fact]
public async Task WelcomeAsync_sends_welcome_mail()
{
var fake = new FakeEmailSender();
var sut = new WelcomeService(fake);
await sut.WelcomeAsync("test@example.com");
Assert.Single(fake.Sent);
Assert.Equal("test@example.com", fake.Sent[0].To);
Assert.Equal("ようこそ!", fake.Sent[0].Subject);
}
}
できた〜!🎉 メール環境ゼロでも、爆速で安定してテストできる⚡💖
6) 2026っぽい最新ネタ 時間は TimeProvider が便利 🕰️✨
「IClock作る」でも全然OKなんだけど、今どきは TimeProvider っていう公式の抽象があるよ〜!
.NET 10 + C# 14 が現行の最新ラインで、C# 14 は .NET 10 でサポートされてるよ。(Microsoft Learn)
さらに、TimeProviderの概要と、テスト向けの FakeTimeProvider も公式で用意されてるのが強い💪✨(Microsoft Learn)
6-1) ロジック側は TimeProvider を受け取る
public sealed class ExpiryChecker
{
private readonly TimeProvider _time;
public ExpiryChecker(TimeProvider time)
=> _time = time;
public bool IsExpired(DateTimeOffset expiresAtUtc)
=> _time.GetUtcNow() >= expiresAtUtc;
}
6-2) テストでは FakeTimeProvider を使う
using Microsoft.Extensions.Time.Testing;
using Xunit;
public class ExpiryCheckerTests
{
[Fact]
public void IsExpired_returns_true_when_time_passed()
{
var fakeTime = new FakeTimeProvider();
fakeTime.SetUtcNow(new DateTimeOffset(2026, 01, 01, 0, 0, 0, TimeSpan.Zero));
var sut = new ExpiryChecker(fakeTime);
var expires = new DateTimeOffset(2025, 12, 31, 23, 0, 0, TimeSpan.Zero);
Assert.True(sut.IsExpired(expires));
}
}
“今”が固定できる って、テストでは神だよね😇🧪✨
(FakeTimeProvider は Microsoft.Extensions.TimeProvider.Testing パッケージで提供されるよ)(Microsoft Learn)
7) 命名のコツ 動詞より「役割」📝✨
命名って悩むよね〜😵💫 でもコツがあるよ!
✅ よくある良い例
IEmailSender✉️IClockorTimeProvider🕰️IFileSystem/IFileStore🗂️IPaymentGateway💳
❌ ありがちNG例
IUtil/IHelper(何する人?ってなる)🙅♀️IService(デカくなりがち)🐘💥IDoEverything(破滅の香り)🔥
8) 粒度のコツ インターフェースは「細め」が正義🧼✨
インターフェースが大きいと、Fake作るのが地獄になるよ〜😇💦
✅ 小さくするコツ
- そのクラスが 本当に必要な操作だけ を入れる
- “汎用” より “用途特化” を優先する
- 迷ったら まず1〜3メソッド にする🧩✨
9) 便利な既製品 IFileSystemなら System.IO.Abstractions も人気 🗂️✨
ファイルI/Oは自作で包んでもいいけど、System.IO.Abstractions みたいに「File/Directory APIを注入可能にした」定番ライブラリもあるよ〜!(GitHub)
(第16章でファイルI/Oをやる時に、採用するか検討すると気持ちいい👍✨)
10) AIの使いどころ この章は相性よすぎ 🤖💖
Copilot/Codexに頼むとラクになるところ👇
使えるお願い例 📝✨
- 「このクラスのI/O部分をインターフェースに抽出して」
- 「このインターフェースのFake実装を作って」
- 「AAA形式のxUnitテストを3つ提案して」
- 「命名を “役割ベース” に直して候補を出して」
鵜呑み禁止ポイント⚠️😆
- インターフェースが 大きくなりすぎてない? 🐘
- ロジックにI/Oが 混ざり直してない? 🔥
- 命名が
IService祭りになってない?🎪
11) ミニ演習 I/Oを包んでテストしよう 🧪🎓✨
お題:次のコードを “包んでテスト可能” にしてみてね
public sealed class PasswordResetService
{
public bool CanReset(DateTimeOffset expiresAtUtc)
=> DateTimeOffset.UtcNow <= expiresAtUtc;
}
ゴール例 🎯
DateTimeOffset.UtcNowをやめるTimeProviderを受け取る(またはIClockを作る)FakeTimeProviderで期限切れテストを書く 🧪✨
(第14章の先取りにもなるよ〜!🕰️💖)
12) まとめ この章の合言葉 📦➡️🌍✨
- 外の世界はインターフェース越し 🚪🧩
- インターフェースは内側に置く 📦✅
- 小さく作って、Fakeで差し替える 🎭🧪
- 時刻は TimeProvider + FakeTimeProvider が強い🕰️💪(Microsoft Learn)
次章チラ見せ 👀✨
次は「Fake / Stub / Mock の使い分け」だよ〜!🎭🧪 この章で作ったインターフェースたちが、そこで超活躍するから楽しみにしててね😊💖