※『先輩、私のコードがバグだらけです!〜TUnit と Moq で始めるユニットテスト入門〜』シリーズは、全 6 回を予定しています。
前回はこちら
登場人物
先輩: .NET 開発歴 10 年。「テストコードは、製品コードの最初のクライアントである」が口癖。映画のエンドロールは最後まで見る派。
後輩: 新人開発者。Moq を覚えて「これで無敵だ!」と調子に乗っている。DI(依存性注入)という言葉を聞くと、注射みたいで痛そうだと感じる。
1. プロローグ:スタントマン、棒立ち疑惑
後輩:「先輩! 見てください! Moq を使って『ユーザー削除機能』のテストを書きました!」
先輩:「お、どれどれ。……うん? これ、本当にテストできてるか?」
後輩:「できてますよ! エラーも出てないし、テスト結果も成功です! DB を消すスタントマン(Mock)もちゃんと雇いました!」
先輩:「いや、コードを見てみろ。スタントマンを雇ってはいるが……『飛び降りろ(削除しろ)』と命令した後、彼が本当に飛び降りたか確認していないぞ」
後輩:「えっ? 雇ったんだから、仕事してますよね?」
先輩:「甘いな。今の状態は、スタントマンを現場に呼んで、ただ棒立ちさせているだけかもしれないぞ。映画(テスト)として成立させるには、『ちゃんと演技をしたか』 を映像確認しなきゃダメだ」
2. 演技チェック「Verify(検証)」
先輩:「ここで登場するのが、Verify(ベリファイ) メソッドだ。これは映画監督がカットの後にモニターをチェックする作業と同じだ」
-
Setup(セットアップ): 撮影前の「演技指導」。監督:「ここで飛び降りるフリをしてくれ」 -
Verify(ベリファイ): 撮影後の「VTR確認」。監督:「今のシーン、指示通りに1回だけ飛び降りたか?」
後輩:「なるほど! Assert は『結果(戻り値)』のチェックで、Verify は『過程(呼び出し)』のチェックなんですね」
先輩:「その通り。特に戻り値がない(void)メソッドや、副作用(DB 更新など)が主目的のメソッド の場合、Verify がないと『何もしてなくてもテストが通る』という怖いことが起きるんだ」
3. 現場への潜入工作「DI(依存性注入)」と「.Object」
後輩:「理屈はわかりました。でも先輩、もう一つ質問が……。このスタントマン(Mock)、どうやってアプリ(撮影現場)の中に送り込むんですか?」
先輩:「いい質問だ。アプリ側は『本物の DB 担当者(IUserRepository)』が来ると思って待っている。そこに偽物のスタントマンを行かせても、警備員に止められるだろう?」
後輩:「ですよね。型が違うって怒られそうです」
先輩:「そこで使うのが、.Object プロパティだ。これは言わば、『精巧な変装セット(身分証付き)』だ」
-
mock: スタントマン本人(演技指導ができる)。 -
mock.Object: 「IUserRepository」の衣装を着た状態。
先輩:「この『変装したスタントマン』を、外からアプリに渡してやる。これを専門用語で『依存性注入(DI:Dependency Injection)』と呼ぶんだ」
後輩:「うげっ、DI……。難しそうな言葉出た……」
先輩:「難しく考えるな。『現地調達(内部で new)』をやめて、『持ち込み(引数で渡す)』にする。ただそれだけのことだ。.Object は “依存先のフリをした実体” を取り出すための出口。それをコンストラクタ引数として渡す設計が DI(依存性注入) だ。」
4. 実践! 削除機能のテストコード
先輩:「じゃあ、実際にコードを書いてみよう。今回は『ID:99 のユーザーを削除する』というシナリオだ」
using Moq;
using TUnit.Core;
public class UserServiceTests
{
[Test]
public async Task RemoveUserAsync_Test()
{
// 1. スタントマンの用意(Mock)
var mockRepo = new Mock<IUserRepository>();
// 2. 演技指導(Setup)
// 「ID:99 の削除を頼まれたら、成功(true)と答えるんだ」
mockRepo.Setup(repo => repo.DeleteUserAsync(99))
.ReturnsAsync(true);
// 3. 変装して現場に潜入!(DI)
var service = new UserService(mockRepo.Object);
// 4. アクション!(実行)
var result = await service.RemoveUserAsync(99);
// 5. 結果の検証(Assert)
await Assert.That(result).IsTrue();
// 6. ★今回の肝★ 演技チェック(Verify)
mockRepo.Verify(repo => repo.DeleteUserAsync(99), Times.Once());
}
}
後輩:「おおお! Verify のところで Times.Once() って書いてある! 『一回だけ呼んだこと』までチェックできるんですね!」
先輩:「そう。もしコードのバグで 2 回削除ボタン押しちゃってたり、逆に 1 回も押してなかったりしたら、ここでテストが失敗(NG)になるわけだ」
5. スタントマンの性格診断「Loose と Strict」
後輩:「へぇ〜。スタントマンって優秀ですね」
先輩:「ちなみに、Moq のスタントマンには 2 種類の性格があることを知っておくといい」
Loose(ルーズ・デフォルト): アドリブ OK な俳優台本
- (Setup)にない演技を振られても、「あー、はいはい(適当な値を返す)」と笑顔で乗り切る。
- テストが落ちにくいので、最初はこれがオススメ。
Strict(ストリクト・厳格): 台本絶対主義の俳優
- 台本にない演技を振られると、「聞いてないよ! 帰る!」とブチギレて撮影放棄(例外スロー) する。
- 「余計なことは一切していない」ことを保証したい、厳しいテストで使う。
先輩:「初心者のうちは、デフォルトの『Loose』なスタントマンと仲良くしておけばいいよ」
6. なぜ TUnit でこれをやるのか?
後輩:「先輩、Moq の使い方は分かりましたけど、これって xUnit とか他のフレームワークでも同じじゃないですか? なぜ TUnit なんですか?」
先輩:「いいところに気づいたな。まず TUnit の特徴から話そう。TUnit は、テストを 『メソッド単位で並列(パラレル)』 に実行する。設定なしで、デフォルトでそうなってるんだ」
後輩:「あ! だから爆速なんですね! でも……xUnit でも並列実行できますよね?」
先輩:「よく知ってるな。xUnit もできる。ただ xUnit はクラス単位が基本で、細かく設定が必要だ。TUnit は メソッド単位でデフォルト並列、しかも DI もネイティブサポートしている。だから、これからやる書き方と自然にフィットするんだ」
後輩:「なるほど……でも並列実行って、何か気をつけることありますか?」
先輩:「ある。100 個のテストがヨーイドンで同時に走り出すとき、もしスタントマン(Mock)を全テストで 1 つだけ使い回していたらどうなる?」
後輩:「えっと……テスト A が『削除チェック』してる最中に、テスト B が勝手に台本(Setup)を書き換えちゃったり……?」
先輩:「ご名答。 大事故(テスト失敗) になる。だから テストごとに毎回 new Mock して渡す スタイルが並列実行の鉄則だ」
後輩:「なるほど! でも先輩、それって Moq じゃなくて本物のインスタンスを毎回 new しても同じじゃないですか?」
先輩:「鋭いな! 実はそこが Moq を使う本当の理由だ。第 3 回を思い出してくれ。後輩くんは何をやらかした?」
後輩:「……開発 DB のテストデータを全部消しました……(遠い目)」
先輩:「そう。本物インスタンスを毎回 new することはできる。でも、こんな状況になるぞ」
UserService
└─ IUserRepository(← 本物を new)
└─ DbContext(← DB 接続が必要)
└─ 接続文字列、トランザクション、ネットワーク……
先輩:「本物インスタンスを渡そうとすると、 その先の依存も全部スーツケースに詰めて連れてくる ことになる。DB が動いてないとテストできない、テストのたびに DB に書き込まれる……第3回の悲劇の繰り返しだ」
後輩:「うわ、それは嫌です。あの恐怖はもう味わいたくない……」
先輩:「だから Moq を使う。Mock を使えば、 『IUserRepository の衣装を着た、依存を一切持たないクリーンなスタントマン』 を毎回 new して渡せる。本物が引きずってしまう『連鎖する依存』を、丸ごと切り離せるんだ。第 3 回で教えた『危険なスタントはプロに任せろ』の真価は、実はここにある」
後輩:「つまり……!」
毎回 new して渡す → TUnit のメソッド単位並列実行で競合しないため
本物ではなく Mock を渡す → 連鎖する依存(DB 等)を切り離すため
後輩:「『毎回 new して渡す』と『Moq を使う』って、別々の理由があったんですね! 今までごっちゃにしてました!」
先輩:「TUnit + DI + Moq、この 3 つが揃って初めて、 『速くて、独立していて、壊れないテスト』 が完成する。バラバラじゃ意味がないんだ」
後輩:「第 3 回でスタントマンを覚えて、今回で現場への送り込み方を覚えて……ピースが全部つながった気がします!」
7. まとめと次回予告
先輩:「よし、今日のまとめだ」
1. Verify: スタントマンがちゃんと演技したか(メソッドが呼ばれたか)を VTR チェックすること。
2. DI: 変装したスタントマン(mock.Object)を、外から渡せるように設計し(依存性注入)、実際に注入すること。
3. 毎回 new して渡す: TUnit のメソッド単位並列実行で、テスト間の競合を防ぐための鉄則。
4. Mock を渡す理由: 本物インスタンスでは連鎖する依存(DB 等)まで引きずってしまう。Moq を使えばそれを丸ごと切り離せる。
5. TUnit + DI + Moq: この 3 つが揃って初めて、『速くて、独立していて、壊れないテスト』が完成する。
後輩:「スタントマン(Moq)をただ呼ぶだけじゃなくて、ちゃんと現場に送り込んで(DI)、仕事ぶりをチェックする(Verify)ところまでがセット。しかも、毎回 new するのは並列実行のため、本物じゃなく Mock を使うのは依存を切り離すため……それぞれにちゃんと理由があるんですね!」
先輩:「完璧な理解だ。……でも後輩くん、気づいてるか?」
後輩:「え? 何をですか?」
先輩:「さっきのテストコード、毎回 new Mock して、Setup して、new Service して……って書くの、面倒くさくないか? テストが 10 個あったら 10 回書くのか?」
後輩:「あーっ! 確かに! 指が腱鞘炎になりそうです!」
先輩:「次回は、そんな君を救う 『テストの準備(Setup)と後片付け』 の話だ。散らかったキッチンじゃ最高の料理は作れないからな」
(第5回へ続く)





