第15章 合成のご褒美🍬:テストがラクになる✅⚡
ここまで「差し替えできる部品🧩」を作ってきたよね? この章は、そのご褒美タイム🍰✨ 「え、テストってこんなに気持ちよく書けるの!?」を体験する回だよ〜😊💕
0. この章でできるようになること🎯✨
- 外部I/O(日時⌚・メール📧・Web🌐・DB🗄️)を Fake/Stub に置き換えてテストする✅
- 「どこをモックして、どこはしないか」判断できるようになる🧠
- テストが 速い⚡・安定✅・読みやすい📖 になる感覚をつかむ
1. なんで「合成」だとテストがラクになるの?🧩➡️✅

テストがしんどい原因、だいたいコレ👇
DateTime.Nowみたいな 時間 に依存⌚(実行タイミングで結果がブレる)- メール送信📧 / HTTP🌐 / ファイル📁 / DB🗄️ みたいな 外部I/O を直接呼ぶ(遅い・落ちる・環境依存)
newがコードの奥深くに埋まってて、差し替えできない😵💫
合成(Composition)で「部品を外から渡す🧩🎁」形にすると…
- テストでは Fake部品 を渡すだけでOK🧸✨
- 本番では 本物部品 を渡して動かす🚀
この「差し替えの入り口」があるだけで、テストが別ゲーになるよ😊✅
2. Fake / Stub / Mock ざっくり使い分け🧸🎭🧪
ふわっとでOK!使ううちに体に染みるやつ🙂✨
- Fake🧸:それっぽく動く「簡易の実装」 例:送信内容をリストに溜めるメール送信Fake📧
- Stub🪵:決まった値を返すだけ 例:常に「成功」を返す決済Stub💳
- Mock🎭:呼び出し回数や引数を検証したい時に使う 例:「Sendが1回だけ呼ばれた?」を確認✅
※テストフレームワークは MSTest / NUnit / xUnit.net などが一般的だよ(最近は TUnit もよく名前を見る)(Microsoft Learn)
3. 今日のお題(ミニ題材)🍬📦
注文確定(Confirm) すると👇
- 合計金額を計算🧮
- 期間限定割引(“今日だけ”)があれば適用🎁
- 確定メールを送る📧
「時間⌚」と「メール📧」が外部要素なので、ここを差し替えてテストするよ✅✨
4. まずは “テストしにくい” 例😱(new と Now が直書き)
public class OrderService
{
public void Confirm(Order order)
{
var now = DateTimeOffset.Now; // ← テストで制御しづらい😵
var discountRate = (now.Day == 15) ? 0.10m : 0m; // 例:毎月15日だけ10%OFF
var total = order.Subtotal * (1 - discountRate);
var mailer = new SmtpMailer(); // ← ここでnewしちゃうと差し替えできない😭
mailer.Send(order.CustomerEmail, $"Total: {total}");
}
}
これ、テストするときに詰むポイントが2つあるよね😇
- 日付が変わるとテスト結果が変わる⌚💥
- テスト実行で本当にメール飛ぶ📧💥(怖すぎ)
5. 合成で “差し替え可能” にする🧩🎁(テストが勝つ形)
5-1. 依存を「契約(interface)」にする🔌
public interface IMailer
{
void Send(string to, string body);
}
時間は System.TimeProvider を使うのが今どきでラクだよ⌚✨
TimeProvider は「テスト可能で予測可能にするための時間の抽象化」として紹介されてるよ(Microsoft Learn)
さらにテスト用の FakeTimeProvider も用意されてる(Microsoft Learn)
5-2. OrderService を合成向きにする🧩
public class OrderService
{
private readonly IMailer _mailer;
private readonly TimeProvider _time;
public OrderService(IMailer mailer, TimeProvider time)
{
_mailer = mailer;
_time = time;
}
public decimal Confirm(Order order)
{
var now = _time.GetLocalNow(); // ← テストで時間を固定できる😊
var discountRate = (now.Day == 15) ? 0.10m : 0m;
var total = order.Subtotal * (1 - discountRate);
_mailer.Send(order.CustomerEmail, $"Total: {total}");
return total;
}
}
public record Order(decimal Subtotal, string CustomerEmail);
ポイントはこれ👇✨
- OrderService の中で new しない
- 時間も外から渡す これだけでテストが超ラクになるよ✅
6. Fake を作って “爆速テスト” する⚡🧸
6-1. メール送信 Fake📧🧸(送った内容を保存するだけ)
public class FakeMailer : IMailer
{
public List<(string To, string Body)> Sent { get; } = new();
public void Send(string to, string body) => Sent.Add((to, body));
}
6-2. 時間を固定する FakeTimeProvider⌚🧊
NuGet でこれを追加するイメージ👇(テストプロジェクト側)
Microsoft.Extensions.TimeProvider.Testing(FakeTimeProvider が入ってる)(NuGet)
7. テストを書こう🧪✅(例:xUnit)
7-1. 15日なら10%OFFになる?🎁
using Microsoft.Extensions.Time.Testing;
using Xunit;
public class OrderServiceTests
{
[Fact]
public void Confirm_On15th_Gives10PercentOff_AndSendsMail()
{
// Arrange
var fakeMailer = new FakeMailer();
var fakeTime = new FakeTimeProvider();
fakeTime.SetLocalTimeZone(TimeZoneInfo.Local);
fakeTime.SetLocalNow(new DateTimeOffset(2026, 1, 15, 10, 0, 0, TimeZoneInfo.Local.BaseUtcOffset));
var sut = new OrderService(fakeMailer, fakeTime);
var order = new Order(1000m, "a@example.com");
// Act
var total = sut.Confirm(order);
// Assert
Assert.Equal(900m, total);
Assert.Single(fakeMailer.Sent);
Assert.Contains("Total: 900", fakeMailer.Sent[0].Body);
}
}
※FakeTimeProvider は “時間依存コードのテストを容易にする” ために使えるよ、って Microsoft のドキュメントにも書かれてるよ(Microsoft Learn)
7-2. 15日じゃないなら割引なし?🙂
[Fact]
public void Confirm_Not15th_NoDiscount()
{
var fakeMailer = new FakeMailer();
var fakeTime = new FakeTimeProvider();
fakeTime.SetLocalNow(new DateTimeOffset(2026, 1, 14, 10, 0, 0, TimeSpan.FromHours(9)));
var sut = new OrderService(fakeMailer, fakeTime);
var order = new Order(1000m, "a@example.com");
var total = sut.Confirm(order);
Assert.Equal(1000m, total);
}
✅ ここまでの感想: テストが ネット不要🌐❌、メール送信なし📧❌、時間ブレなし⌚❌ で、秒速で回る⚡✨ これが「合成のご褒美」だよ〜🍬😊
8. 「何をモックし、何をモックしない?」の目安🧭✨
モックしがちだけど、しない方がラクなこと多いよ🙂
モック(差し替え)しやすいもの✅
- 時間⌚(TimeProvider)
- ランダム🎲
- HTTP🌐 / DB🗄️ / ファイル📁 / メール📧
- OS依存(環境変数、時計、スレッドタイミング)🪟
モックしない(本物でOK)になりやすいもの🧡
- ただの計算🧮
- 文字列整形📄
- 値オブジェクト(Money とか)💰
「外の世界」と「中のロジック」を分けるほど、テストが強くなる💪✨
9. Mockフレームワークはいつ使う?🎭
手書きFakeが最強な場面、多いよ🧸✨ でも「呼ばれ方の検証」が増えるなら Mock が便利!
有名どころだと Moq(GitHub でも定番)(GitHub) ただし最初は 手書きFake で全然OKだよ😊
10. AI活用🤖🫶(テストが増える瞬間にめっちゃ効く)
使える頼み方テンプレ💬✨
- 「この仕様のテストケースを10個、重複なしで出して🧪」
- 「境界値と例外系を中心にテスト観点を出して⚠️」
- 「この依存はFakeで十分?Mockが必要?理由つきで教えて🧠」
- 「命名を読みやすくして(AAAに沿って)✍️」
👉 ただし “採用するのは自分” ね😊(AIの案は混ざり物もあるから✅)
11. ミニ課題(15分)📮✨
- 割引ルールを「毎月15日」じゃなくて「週末だけ」に変えて、テストも更新してみよ📅🎁
- メール本文に「注文ID」を入れて、FakeMailer で検証してみよ🧾📧
- 例外系:「メールアドレスが空なら送らない」ルールを足してテストしてみよ🧪🛡️
12. 今日のまとめ🌈✅
- 合成で「差し替え口🧩」を作ると、テストは速く・安定して・気持ちよくなる⚡
TimeProvider+FakeTimeProviderで “時間ガチャ” を消せる⌚🧊(Microsoft Learn)- テストフレームワーク周りも最新では整理が進んでて、
dotnet testでも VSTest / Microsoft.Testing.Platform の話が出てくる(混在は注意)(Microsoft Learn)
次章(第16章)は「継承→合成へ安全に移行🛠️🙂」だね! この第15章で “テストの気持ちよさ” を掴んでおくと、移行作業がめっちゃ怖くなくなるよ〜🍀😊