1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第4回:「このスタントマン、演技が細かいぞ」〜Verify と DI の関係〜

1
Last updated at Posted at 2026-03-31

『先輩、私のコードがバグだらけです!〜TUnit と Moq で始めるユニットテスト入門〜』シリーズは、全 6 回を予定しています。

前回はこちら

登場人物

先輩: .NET 開発歴 10 年。「テストコードは、製品コードの最初のクライアントである」が口癖。映画のエンドロールは最後まで見る派。

後輩: 新人開発者。Moq を覚えて「これで無敵だ!」と調子に乗っている。DI(依存性注入)という言葉を聞くと、注射みたいで痛そうだと感じる。


1. プロローグ:スタントマン、棒立ち疑惑

後輩:「先輩! 見てください! Moq を使って『ユーザー削除機能』のテストを書きました!」

先輩:「お、どれどれ。……うん? これ、本当にテストできてるか?」

後輩:「できてますよ! エラーも出てないし、テスト結果も成功です! DB を消すスタントマン(Mock)もちゃんと雇いました!」

先輩:「いや、コードを見てみろ。スタントマンを雇ってはいるが……『飛び降りろ(削除しろ)』と命令した後、彼が本当に飛び降りたか確認していないぞ」

後輩:「えっ? 雇ったんだから、仕事してますよね?」

先輩:「甘いな。今の状態は、スタントマンを現場に呼んで、ただ棒立ちさせているだけかもしれないぞ。映画(テスト)として成立させるには、『ちゃんと演技をしたか』 を映像確認しなきゃダメだ」

4-1.png

2. 演技チェック「Verify(検証)」

先輩:「ここで登場するのが、Verify(ベリファイ) メソッドだ。これは映画監督がカットの後にモニターをチェックする作業と同じだ」

  • Setup(セットアップ): 撮影前の「演技指導」。監督:「ここで飛び降りるフリをしてくれ」
  • Verify(ベリファイ): 撮影後の「VTR確認」。監督:「今のシーン、指示通りに1回だけ飛び降りたか?」

後輩:「なるほど! Assert は『結果(戻り値)』のチェックで、Verify は『過程(呼び出し)』のチェックなんですね」

先輩:「その通り。特に戻り値がない(void)メソッドや、副作用(DB 更新など)が主目的のメソッド の場合、Verify がないと『何もしてなくてもテストが通る』という怖いことが起きるんだ」

4-2.png

3. 現場への潜入工作「DI(依存性注入)」と「.Object」

後輩:「理屈はわかりました。でも先輩、もう一つ質問が……。このスタントマン(Mock)、どうやってアプリ(撮影現場)の中に送り込むんですか?」

先輩:「いい質問だ。アプリ側は『本物の DB 担当者(IUserRepository)』が来ると思って待っている。そこに偽物のスタントマンを行かせても、警備員に止められるだろう?」

後輩:「ですよね。型が違うって怒られそうです」

先輩:「そこで使うのが、.Object プロパティだ。これは言わば、『精巧な変装セット(身分証付き)』だ」

  • mock: スタントマン本人(演技指導ができる)。
  • mock.Object:IUserRepository」の衣装を着た状態。

先輩:「この『変装したスタントマン』を、外からアプリに渡してやる。これを専門用語で『依存性注入(DI:Dependency Injection)』と呼ぶんだ」

後輩:「うげっ、DI……。難しそうな言葉出た……」

4-3.png

先輩:「難しく考えるな。『現地調達(内部で new)』をやめて、『持ち込み(引数で渡す)』にする。ただそれだけのことだ。.Object は “依存先のフリをした実体” を取り出すための出口。それをコンストラクタ引数として渡す設計DI(依存性注入) だ。」

4. 実践! 削除機能のテストコード

先輩:「じゃあ、実際にコードを書いてみよう。今回は『ID:99 のユーザーを削除する』というシナリオだ」

UserServiceTests.cs
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)になるわけだ」

4-4.png

5. スタントマンの性格診断「Loose と Strict」

後輩:「へぇ〜。スタントマンって優秀ですね」

先輩:「ちなみに、Moq のスタントマンには 2 種類の性格があることを知っておくといい」

Loose(ルーズ・デフォルト): アドリブ OK な俳優台本

  • (Setup)にない演技を振られても、「あー、はいはい(適当な値を返す)」と笑顔で乗り切る。
  • テストが落ちにくいので、最初はこれがオススメ。

Strict(ストリクト・厳格): 台本絶対主義の俳優

  • 台本にない演技を振られると、「聞いてないよ! 帰る!」とブチギレて撮影放棄(例外スロー) する。
  • 「余計なことは一切していない」ことを保証したい、厳しいテストで使う。

先輩:「初心者のうちは、デフォルトの『Loose』なスタントマンと仲良くしておけばいいよ」

4-5.png

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回の悲劇の繰り返しだ」

4-6.png

後輩:「うわ、それは嫌です。あの恐怖はもう味わいたくない……」

先輩:「だから 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回へ続く)

1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?