JJUG CCC 2025 Springの登壇スライドを補足する記事です。スライドだけだと文脈が失われてしまうので、それを補うことを目的としています。話した内容の文字起こしではありません。
Docswellで公開したスライドを別窓で開きながら、本文と照らし合わせて見て頂ければと思います。
「現在(あるいは直近で)関わっているプロジェクトでユニットテストは書かれているか?」という挙手アンケートの結果は、約8割の方がYESでした。10年前なら半分行くかどうかだったと思うので、ユニットテストを書くこと自体はかなり普及したと感じます。一方で、ユニットテストを十分に書けているか、上手に書けているか、という問いに対しては自信を持てない方も少なくないでしょう。
今の時代、テストコードは生成AIに書かせればいいのではないかという意見もあります。一年前だと精度がまだまだでしたが、AIエージェントの登場とLLMのモデル性能向上により、AIが普通にテストコードを書けるようになりました。今回登壇にあたり用意したサンプルコードも、大半をAIに書いてもらいました(使用したのは、Cursor + Gemini 2.5 Pro、GitHub Copilot + Claude Sonnet 3.5)。
それでも、AIにテストコード作成を「まるっと」任せっきりにすべきではないと、私は考えます。理由は2つ。
- 雑にテストコードを書かせても、生成されるテストコードの質は平均点以下となる。どのような戦略で、どのようなテストケースを記述するのかは人間が考える必要がある
- AIは間違える。プロダクションコードの誤りは、最終的にテストで検知するチャンスがあるが、テストコードの誤りについてはそれがない
なので2025年の6月時点の現状としては、人間がテストの戦略を立て、テストの妥当性を評価する必要があると考えています。
まずはユニットテストとは何か、の定義を確認しました。E2Eテストとインテグレーションテストとの境界はかなり明確なのに対して、インテグレーションテストとユニットテストとの境界は曖昧です。そのため、Google社では、テストサイズ(S/M/L)による分類が標準的に用いられているそうです。
書籍『単体テストの考え方/使い方』によると、ユニットテストの定義は以下とされます。
- 1単位の振る舞い(a unit of Behavior)を検証すること
- 実行時間が短いこと
- 他のテスト・ケースから隔離された状態で実行されること
上記はテスト駆動開発の流派(古典学派、ロンドン学派)のうち、古典学派のスタンスでの定義となります。どちらが絶対的に正しいという話ではないのですが、このセッションでは以降古典学派のスタンスで話を進めています。
(余談になりますが、テスト駆動開発という文脈から切り離して、これら2つのテストコードの書き方を論じ評価を下すのはフェアでないと考えています)。
ユニットテストの主要な目的をp.20に挙げましたが、端的にいうと、開発者がプロダクションコードに対して自信と安心を持てるようにすることがユニットテストの存在意義だと思います。
シフトレフトと言う考え方の意義については今さら強調も不要でしょう(聞いたことがない、という方は拙書『アーキテクトの教科書』の第5章をご参照ください)。シフトレフトの実践には、QAエンジニアだけでなく、開発者の主体的な関与も重要となります。具体的な活動の一つが、開発者テストとしてのユニットテストの作成です。
私はソフトウェアをテスト(検証)する以上の意味がユニットテストにはあると考えておりまして、だからこそ「良いテストコード」を書くことにもっとこだわるべきだと思うのです。
Part2では、「映画チケット料金計算」のサンプルアプリ(p.26)を題材に、より具体的な話をしました。
Part1でユニットテストの定義を明らかにしましたが、「1単位の振る舞い」ってそもそも何でしょうか。どう識別すればよいのでしょうか?
まずは、全ての仕様をたくさんのif文を駆使して実装した、一枚岩のサービスを提示しました(p.28)。いわゆるトランザクションスクリプトです。そのサービスに対して、ユニットテストを書くようにAIに指示すると、それなりのものを作ってくれます(p.29)。
いくつかの観点でテストケースを分類し、総数は34ケース。コードカバレッジも高いです。ただし本当に網羅性が十分なのかは、数字だけでは語れません。悩んだ開発者が、近くにいたQAエンジニアを捕まえて、テストケース数についてアドバイスを求めた、というストーリーで出てきたのが次のスライドの意見です(p.31)。
経験の浅い開発者だと、組み合わせテストの考え方についてよく知らない人も多いと考えられるため、その考え方を知っておいてもらいたいというのが狙いでした。決して、QAエンジニアはこういうことを言いがちだとか、開発者テストに口出しをしてくるといった意図は一切ないことを記させて頂きます。
ポイントは、コードカバレッジやテストケース数という数値はあくまで参考値であって、それだけでテストの十分性や品質を評価することは危ういということです。ではどうしたらよいのか? テストの観点をきちんと整理してテストケースを設計することです。
トランザクションスクリプトは振る舞いの単位として大きすぎて、開発者が妥当なテストケース設計を行うには、扱いにくいのです。つまりユニットテストには不向きです。「Divide and Concur(分割して統治せよ」の格言に従って、小さく振る舞いに分割することが必要です。
小さく分割すれば、各々のビジネスロジックに入力される因子(パラメータ)数は少なくなり、扱いやすくなります(p.37)。
因子数が減れば、組み合わせ数も減るので、テスト条件について考えやすくなるのです(p.39)。
Part3では、質の良いテストコードを書くために知っておくとよいこととして、いくつかのトピックをお話ししました。
テストコードのSOS (p.42-56)
散らかりがちなテストコードをきれいな状態に保つためのポイントとして、「テストコードのSOS」について説明しました。
- 構造化されている (Structured)
- 整理されている (Organized)
- 自己文書化されている (Self-documenting)
Developers Summit 2025のセッション『リーダブルテストコード~メンテナンスしやすいテストコードを作成する方法を考える~』の中で、t-wadaさんが「テストコードをレビューするときは、まずツリー構造を見る」と仰られていたのですが、会場では「ツリー構造」がピンと来ていない人もいたようです。
テストコードに階層構造を持たせることにより、全体を俯瞰して見ることが可能となります(p.46)。
AAA(トリプリエー)パターンはだいぶメジャーになったと思いますが、「Arrange-Act-Assert-Act-Assertにように繰り返すのはNG(Eager Testと呼ばれるアンチパターン)」というのは知らなかったという声もあり、まだまだユニットテストのプラクティスは普及しきっていないとも感じました(p.54)。
大きな振る舞いに対するユニットテスト (p.57-p.60)
Part2で、振る舞いを小さく分割することでテスト設計が容易となり、ユニットテストにおいて十分な網羅性を担保できるようになるという話をしました。
ですが、抽象度を上げて、コンポーネントが提供する大きな振る舞いとして見た場合のテストはどう考えるべきでしょうか? 結局、サービスに入力される複数因子を組み合わせて網羅的なテストを書くことになるのでしょうか?(p.60)
これについて、テスト戦略次第というお話をしました。お金に関わるロジックなので安全を取って網羅的にテストする、というのも合理的な戦略です。そういった理由がなければ、代表的なパターンとエッジケースを中心にテストすれば十分だと考えることもできます。
ただし、以下の条件付きです。
- 個々の小さなビジネスロジックは網羅的にテストされていること
- ビジネスロジックを呼び出すアプリケーションサービスは、処理フロー制御に専念し、決してビジネスロジックは記述されていないこと
テストダブルの使い方 (p.61-p.69)
テストダブルには様々な種類がありますが、基礎講座ということで、代表的な2つ(テストスタブとモックオブジェクト)についてお話をしました。
何らかの理由で間接入力を制御したい場合に用いるのがテストスタブ。何らかの理由で間接出力を観測したい場合に用いるのがモックオブジェクトです。
Mockitoだとどちらも mock
関数を使って生成するので、それらの区別ができていない方も一定数いそうでしたね。
テストスタブは大変便利なのですが、使わずに済むならそれに越したことはありません。その上で、テストスタブについては目的を理解した上で適切に使うのはOK、モックオブジェクトについては極めて慎重にという話をしました。
講演内容のまとめについては、スライドのp.74-p.76をご参照ください。
今回のスライドは、DocswellのPV数ベースで、2025年2月のデブサミ登壇資料と比較しても倍以上のアクセスとなっており、こんなに多くの方にご覧頂けるとは正直驚きでした。
登壇スライドは、当日聴きに来てくださった方々をメインターゲットとし、彼らの便益向上を最大の目的として公開をしております。とはいえ、当日参加できなかった方や、SNSで流れてきてたまたま目を通した方もいらっしゃると思います。反響の大きかったスライドということもあり、少しスライドを補足するための資料として、当記事を執筆した次第です。
Views: 0