※『先輩、私のコードがバグだらけです!〜TUnit と Moq で始めるユニットテスト入門〜』シリーズは、全 6 回です。
前回はこちら
登場人物
先輩 : .NET 開発歴 10 年。「DRY」が座右の銘。テストコードのないリリース作業は、命綱なしのバンジージャンプだと思っている。
後輩 : 新人開発者。TUnit の修行を通じて、コピペ職人からテスト駆動エンジニアへと進化しつつある。
1. プロローグ:なぜこんなに速いのか?
後輩:「先輩……変なんです」
先輩:「どうした? またテストが全部失敗したか?」
後輩:「いえ、逆です。全部成功してるんですが……速すぎるんです。さっきテストを 100 ケース追加したのに、Visual Studio のテストエクスプローラーの完了時間がほとんど変わっていません。もしかして、サボって実行してないんじゃ……?」
先輩:「フッ、気づいたか。それが TUnit の真骨頂、『完全並列実行(Parallel Execution)』だ」
後輩:「並列……? つまり、全部同時に動いてるってことですか?」
先輩:「その通り。従来のテストランナーは、基本的に『1つ終わったら次』というリレー方式だった。だが TUnit は違う。『よーいドン!』で全員同時に走り出す徒競走なんだ」
2. 並列実行の落とし穴:「staticおじさん」の排除
先輩:「だが、この速さには代償がある。並列で走るためには、『コース(メモリ)』を共有してはいけないという絶対のルールがある」
後輩:「共有? どういうことですか?」
先輩:「もし、全テストで使い回す『静的変数(static field)』があったらどうなる?」
// 【TUnitには向かない例】
public static class UserDatabase
{
// 全員でこのリストを共有してしまう!
// テストAが追加中に、テストBが削除するとクラッシュする
public static List<string> Users = new List<string>();
}
先輩:「これを『テスト汚染』と呼ぶ。並列実行のテストでは、 共有される static な状態は深刻な不具合の原因になりやすい。だからこそ、第 4 回で教えた **『毎回 new して DI する(インスタンス化)』という書き方が重要になってくるんだ」
後輩:「ああっ! だから先輩は『使い回すな』って口酸っぱく言ってたんですね! あれは並列実行での事故を防ぐためだったのか……!」
先輩:「そう。TUnit を使うということは、『static おじさん(グローバルな状態に依存する古い設計)』との決別を意味するんだ」
3. 最後の罠:非同期アサーション await Assert
先輩:「さて、TUnit ならではの重要なルールを一つ教えよう。今まで何気なく書いていた Assert だが……」
// TUnitの流儀
[Test]
public async Task CalculatorAdd_Test()
{
var result = Calculator.Add(1, 2);
// × 間違い:検証が終わる前にテストが終了してしまう!
// Assert.That(result).IsEqualTo(3);
// ○ 正解:検証完了を待機する
// ※ TUnitでは Assert 自体が非同期 API として設計されているため、await が必須となります。
await Assert.That(result).IsEqualTo(3);
}
後輩:「そういえば、なんで Assert に await が付いてるんですか? 検証なんて一瞬で終わるのに」
先輩:「TUnit は、アサーション自体も非同期で動くように作られている。もし await を忘れると、『検証結果が出る前にテストメソッドが終了し、合格扱いになってしまう』という恐ろしいことが起きる」
後輩:「ひえっ! バグがあるのにテスト成功!? それ一番怖いじゃないですか!」
先輩:「そうだ。だから TUnit では、『息をするように await を書け』。それが未来のバグを防ぐ唯一の方法だ」
4. 爆速の向こう側:Native AOT への対応
後輩:「並列実行に非同期アサーション……TUnit って、とことん『現代的なC#』に特化してるんですね」
先輩:「ああ。そしてもう一つ、これからの時代に欠かせない最強の武器がある。『Native AOT(ネイティブ・エーオーティー)』への完全対応だ」
後輩:「AOT? 最近ニュースでよく見ますけど、起動が爆速になるやつですよね? でも設定が難しそう……」
先輩:「いや、TUnit なら簡単だ。方法は 2 つある。好きな方を選べ」
方法A:XML を直接編集する(硬派な君へ)
先輩:「Visual Studio でプロジェクトファイル(.csproj)を開いて、この設定を追加するだけでいい」
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TUnit" Version="0.1.0" />
</ItemGroup>
</Project>
方法B:Visual Studioの画面で設定する(UI 派の君へ)
後輩:「先輩、XML いじるのちょっと怖いです……。マウスでポチッとできませんか?」
先輩:「できるぞ。Visual Studio のプロパティ画面を使えばいい」
後輩:「おおっ! チェックを入れるだけ! これなら僕でも安心です!」
先輩:「従来のフレームワーク(xUnit など)は『リフレクション』を多用していたから AOT 化すると動かなくなることが多かった。だが TUnit は『Source Generator』でコンパイル時にコードを生成するから、AOT と相性抜群なんだ」
後輩:「つまり、これから僕たちが作るアプリがクラウドやコンテナで AOT 化されても、TUnit なら問題なくテストできるってことですね!」
先輩:「そういうことだ。.NET 10 時代のクラウドネイティブ開発において、これは大きなアドバンテージになる」

※ Native AOT では一部の動的機能(動的生成・一部のライブラリ)が制限されるため、利用ライブラリの対応状況は事前に確認しましょう。
5. エピローグ:テストは君を守る盾
後輩:「……終わりました。TUnit のインストールから始まって、Moq でのスタントマン、データ駆動テスト、そして Native AOT の設定まで。なんか、プログラマーとして少し強くなれた気がします」
先輩:「ああ。最初の頃、手動でポチポチ画面を操作していた頃とは顔つきが違うな」
後輩:「先輩、僕そろそろ今回の機能をリリースしてきます。いつもなら『バグが出たらどうしよう』って胃が痛くなるんですけど……今日は平気です」
先輩:「なぜだ?」
後輩:「だって、僕の後ろには数百人のスタントマン(テストコード)がいて、1秒で全部チェックしてくれてますから!」
先輩:「……いい答えだ」
先輩:「いいか後輩。テストコードは、面倒な義務じゃない。 『未来の自分』と『チーム』を守るための最強の盾だ。この盾がある限り、エンジニアは何度でも大胆に挑戦できる。リファクタリングも、機能追加も、怖くない」
後輩:「はい! 行ってきます!」
先輩:「(……ふん、あいつも一人前になったな。さて、俺も自分のコードに await 付け忘れてないか見直すかw)」
【連載終了】これからのエンジニアへ
TUnit は、.NET 8 以降で利用でき、今後の .NET の進化を見据えたモダンな設計思想(非同期・並列・Source Generator・Native AOT)を先取りした次世代のフレームワークです。
この連載で紹介したテクニックは、TUnit に限らず、あらゆるテストフレームワークに通じる 「良いテストの書き方」 の基礎です。
良いテストの書き方
環境分離 : テストと本番を混ぜない。
依存分離 : Moq で外部依存を断ち切る。
データ駆動 : パターンを網羅する。
状態分離 : 並列実行に備えて Static を避ける。
未来志向 : .csproj またはプロパティ画面で AOT を有効化し、モダンな技術スタックを選ぶ。
さあ、あなたも今すぐ NuGet から TUnit をインストールして、爆速のテストライフを始めましょう!
(完)




