※ユニットテストについて人から質問されたとき、自分でも理解があいまいだと気づき、改めて学び直しました。その過程で感じた疑問や学んだことを、架空の「先輩」と「後輩」のキャラクターによる会話形式でまとめています。ユニットテストをこれから学ぶ方の助けになれば幸いです。
※『先輩、私のコードがバグだらけです!〜TUnit と Moq で始めるユニットテスト入門〜』シリーズは、全 6 回を予定しています。
登場人物
先輩(28歳): フルスタック開発者。面倒見が良いが、コードの品質にはうるさい。最近は TUnit にハマっている。
後輩(23歳): 入社 2 年目の元気な開発者。動くコードを書くのは早いが、テストコードを書くのは「時間がもったいない」と思っている。
プロローグ:深夜のバグ修正
後輩:「はぁ……。やっと修正終わりました……」
先輩:「お疲れ。今回のバグ、原因は何だったの?」
後輩:「『会員登録画面』で、名前にスペースが入っているとエラーになるバグでした。先週追加した新機能のコードが、既存のロジックを壊してたみたいです。もっと早く気づければ、こんな夜中まで残業しなくて済んだのに……」
先輩:「だから言っただろ? 『ユニットテスト』 をちゃんと書こうって」
後輩:「またその話ですか〜。でも先輩、テストコード書くのって、本番のコード書く倍くらい時間かかるじゃないですか。納期もギリギリだったし、とりあえず手動で動かして確認したから大丈夫だと思ったんですよ」
先輩:「その『とりあえず手動で確認』が、今回の残業を生んだんだよ。いい機会だ。今日はなぜテストが必要なのか、そして最新のツールを使うとそれがどう変わるのか、徹底的に教えるよ」
1. ユニットテストってなに? 〜おもちゃの工場の秘密〜
先輩:「子供の頃おもちゃのブロックで遊んだことある?」
後輩:「ありますよ! お城とか宇宙船とか作ってました」
先輩:「じゃあ想像してみて。君がおもちゃのブロックを作る工場の工場長だとする。出荷する製品は『巨大なお城セット』だ。このお城の品質を保証するために、君ならどうする?」
後輩:「完成したお城を組み立ててみて、壊れないかチェックしますね」
先輩:「それが今回の失敗の原因だ。お城を全部組み上げた後に、一番下の土台のブロックがひび割れていて持ち上げた瞬間『あっ!、土台ブロックが壊れて全部崩れちゃった!』ってなったらどうする?」
後輩:「えっ……ひび割れたブロックを交換して、全部バラして、最初から作り直しですね。うわ、それはキツい」
先輩:「そうだろ? 『全て組み上がってから確認する』 のは、手戻りが大きすぎてリスクが高いんだ。だから優秀な工場ではこうする。『ブロック一つひとつが生産された瞬間に、形や大きさが正しいかレーザーで検査する』」
後輩:「なるほど。それなら、お城を組み立てる前に不良品を弾けますね」
先輩:「その『ブロック一つひとつの検査』こそが 『ユニットテスト(単体テスト)』 なんだ。
- ユニット: プログラムの最小単位(クラスやメソッド)。つまりブロック 1 個
- テスト: そのブロックが設計図通りに動くか検証すること
これをしておけば、後でお城(アプリ全体)を組んだ時に、ブロック自体のせいで崩れることはなくなる」
2. 「シフトレフト」〜バグは早めに見つけるほど安い〜
先輩:「開発の世界には 『シフトレフト(Shift Left)』 という言葉があるんだ」
後輩:「左に寄るんですか?」
先輩:「開発の流れを『設計 → 実装 → テスト → リリース』という右に進む一本線(タイムライン)だと考えてごらん。右に行けば行くほど、つまりリリースに近づくほど、バグを見つけた時の修正コストは跳ね上がる」
後輩:「あ、さっきの『お城を全部バラす』話ですね」
先輩:「そう。リリース後にバグが見つかると、修正コストは最初の 100 倍 にもなると言われているんだ。」
設計段階(左側)で気づく: 消しゴムで直すだけ
実装中にユニットテストで気づく: 1 分で直せる
リリース後(右側)に気づく: 深夜残業、顧客への謝罪、データの復旧……プライスレス(大損害)
先輩: 「だから、テストという工程をできるだけタイムラインの『左側』、つまりコードを書いている最中に持ってこよう、というのがシフトレフトの考え方なんだ」
3. テストは「科学実験」であるべし 〜決定論的実行〜
後輩:「わかりました、テスト書きますよ。でも、僕のコード、動かすたびに結果が変わることがあるんですよね。ネットワークの調子とかで」
先輩:「それはユニットテストとしては失格だな。ユニットテストで一番大事なのは 『決定論的(Deterministic)』 であることだ」
後輩:「決定論……? 難しそうな言葉ですね」
先輩:「自動販売機をイメージして。100 円入れてボタンを押したら、必ずジュースが出てくるよね? もし『今日は気分がいいからジュース出すけど、明日は出さないかも』なんて自販機があったらどう思う?」
後輩:「不良品ですね。叩いて直します!」
先輩:「余計に壊れるぞw!プログラムも自動販売機と同じ考え方だ。」
- 同じ入力(100 円入れる)を与えたら
- いつ、どこで、誰が実行しても
- 必ず同じ出力(ジュースが出る)にならなきゃいけない
先輩:「これを『決定論的』と言うんだ。ネットワークにつながったり、データベースの現在時刻に依存したりすると、実行するたびに結果が変わってしまう。これを防ぐ技術が、後で教える『Moq(モック)』なんだけど、今は 『いつでも同じ結果になるテストしか信じちゃいけない』 とだけ覚えておいて」
4. 歴史を変える新ツール「TUnit」登場
後輩:「へぇ、テストにも色々あるんですね。で、そのテストを書くためのツールって何を使うんですか? 学校では『NUnit』とか『xUnit』って習いましたけど」
先輩:「お、よく知ってるな。これまでの歴史をざっくり整理しよう。」
- NUnit(エヌユニット): 昔からある王道。機能が豊富で歴史が長い。レジェンド的存在
-
xUnit(エックスユニット): NUnit の開発者が『もっとモダンにしようぜ』って作った今の主流
先輩: 「テスト同士が影響し合わないように作られていて、これまで僕らもこれを使っていた。」
先輩:「でも、僕らが今回使うのは、この歴史を塗り替える最新鋭のフレームワーク、『TUnit(ティーユニット)』 だ!」
後輩:「TUnit? 聞いたことないです。何が違うんですか?」
先輩:「最大の違いは、『コンパイルの仕組み』 だ。 これまでの NUnit や xUnit は、プログラムを実行した『後』に、テストコードを探し出して実行していた(リフレクション方式)。 でも TUnitは、コードを書いている最中(コンパイル時)にテストコードを生成してしまう(Source Generator 方式)んだ」
後輩:「えっと……つまり?」
先輩:「宿題に例えるならこうだ。」
従来(xUnit など): 宿題をノートに書いて提出し、先生が放課後に採点して、翌日返ってくる
TUnit: タブレットで宿題をやっていて、一文字書くたびに『そこ間違ってるよ!』って即座に教えてくれる
先輩:「しかも、余計な処理をしないから、テストの実行速度が爆速だ。さらに 『AOT(Ahead-Of-Time)』 という最新のコンパイル技術にも対応していて、クラウド環境でもサクサク動く」
後輩:「おお! 『爆速』って響き、いいですね! 待ち時間が減るならやる気出そうです!」
先輩:「だろ? 開発体験(DX)が劇的に良くなるんだ。 今日は『なぜテストが必要か』を話したけど、次回からは実際にこの TUnit を使って、サクサク動くテストコードを書いていこう!」
後輩:「はい! 先輩、TUnit の使い方、早く教えてください!」
まとめ
ユニットテストとは: アプリ(お城)を組む前に、部品(ブロック)単体を検査すること
シフトレフト: バグは後で見つけるほど高くつく。コードを書く段階(左側)で潰すのが一番お得
決定論的実行: テストは「自販機」のように、いつでも同じ結果にならなければならない
TUnit: コンパイル時に動く最新の超高速テストフレームワーク。これからの主役
(第2回へ続く)