featureテストを整理しよう

Date2026/10/01 Last Modified2026/10/01

概要

Laravelのテストが長く、エージェント駆動開発と非常に相性が悪くなってきたので、軽量化のために色々調べてみようと思った。
全体テストに10分近くかかっていて、作業を依頼するたびに必ず最後に全体テストをやるので、さすがにボトルネックすぎるなと。

環境

Docker
PHP 8.5.11
Laravel 13
PHPUnit 12.5.36

なんでこうなっちゃったのか

恥ずかしながら、テストの中に何を書いているかというのをあんまり詳しく見ていなかったのが原因ではある。
何か機能を追加するたびに、エージェントがテストを増やしていく。ハーネスの条件をテストの通過に重きを置いてやっていた。
test専用の、客観的な立場を持つサブエージェントも設置して、一応良いテストにしようとする段取りだけは作っていた。
段取りは良かったけど、やっぱり具体的な内容は全任せしてしまっていたので、ついに我慢できないくらいテストが重くなってから事の重大さを考え始めたというわけである。

賢いエージェントでも悪いテストができてしまう理由

適当に1つのテストクラスを抽出して、内容をChatGPTなどに読ませてフラットな回答を求めてみると、だいたい問題点が見つかる。
使っているモデルに差はあれど(SolとLuna)、そんなにLunaだから間違えるってこともないはず。なので、Codexエージェントとして使っているときは、なんらかの原因で愚かな視点になってしまっている。

これは結局のところ、AIはちゃんと言われた責任を果たしているだけなので指示が問題なのである。
例えば既存の機能を修正するとする。そのときに既存のテストを削除してしまうよりは、テストを追加して上塗りしたほうが互換性を保てていて安全と考える。
他にも似たようなロジックがあって、共通化できそうなテストがあるとする。でもそれを調べると指示を受けた以上のコストをかけて調査することになる。
そうなると、指示外の作業ということになってしまい、とりあえずハーネスの条件である「テストが通ること」というのを基準に進めてしまう。

その結果、テストクラス名に対してテストを詰めすぎたり、Unitで済むところをFeatureに全部くっつけたりと、いびつなテストケースが増えてくる。
production codeを見ていないtest専用のサブエージェントに俯瞰して見せたところで、サブエージェントもまた渡されたコンテキスト内で判断してしまうから、やっぱり自分から影響範囲を増やすようなことはあんまりやんないと思う。
指示にないことはやらず、粛々と対応してくれることの弊害がこんな感じで出てしまう。きれいなテストは開発者の腕っぷしにかかっているといっても過言ではない。

テストを改善しよう。でもどこから手を付けようか

やるべきことは3つあると思っていて、

  1. 良いテスト、悪いテストを見抜くための知識が人間に必要
  2. 全体のボトルネックを調べて、大きなミスがあるのか、小さなミスの積み重ねなのかを調べて方針を決める
  3. 最後にエージェントのルールとして新しく定義して、これからに活かす という感じ。そもそも私自身が的確な指示を出せなければ、またブラックボックス化してしまう。
    まずは「テストの改善方法」について、実際のファイルをもとに調べてみることにした。

学んだことを箇条書きにする

  • Unitテストとfeatureテストの境界はしっかり分けよう 画面表示を確かめるテストで、refreshDatabaseからユーザーを作成し、actingAs()でやる必要まであるのか?
    ユーザー登録を行ったら、その画面へアクセスできる、というテストは別途共通で置いて、Bladeにデータを渡すところはUnitテストでよいのではないか。
    逆もまた然り。UnitテストにPure Unitを書かずworkflowにしてはいけない。(「UnitテストのうちPure Unitが7%しかないとは衝撃です。」とChatGPTはお怒りでした)

  • テストができた背景を考えよう ある時、バリデーションエラーで英語が出てしまったので日本語メッセージを適用することにしたのだが、これのテストとして
    「ユーザーを作成し、そのユーザーで特定の画面で特定のメッセージを出す」という極端なテストケースができていた。
    特に何も背景を伝えずに分離してほしいと頼んだら「このテストは特定の権限を持つ人に、特定のメッセージを見せるためのテストなので妥当です。しいて言えばFeatureとUnitが混ざってるので分離しましょう」という回答になった。
    けれど「全体的に日本語でエラーメッセージを出したくて、ある1つのページの改修を頼んだ時に作られたテストだ」と伝えた結果、評価が180度変わった。
    その結果「バリデーションのLocaleテスト」「メッセージ内容のテスト」「ユーザー登録と権限のテスト」に分けるという結果で納得した。

  • テストの日付に気を付ける、そのほか変動値も todayを使っていると、日付を計算する系のテストが不安定になる。本当にtodayの必要があるならそうだが、基本は日時を固定して正常系と異常系を作った方が良い。
    乱数やUUIDなどもそう。見落としがちだが、外部のレスポンスなどもfakeで固定しよう。変動するから。そりゃそうか。

  • そもそもクラス名と内容が違いすぎる AIにやらせていたらこのあたりは綺麗にしてくれるのかな……と思っていたが、ふたを開けてみたらぐちゃぐちゃだった。
    細部で見れば合っているのだけれど、その場のつじつま合わせで1メソッドあたりの肥大化がすごい。
    「責務が曖昧です」と伝えるとすぐに改善案が湧きだしてくるので、ここら辺は本当に「成功しているテストは触っちゃいけない」という堅い意志を感じる。

  • BladeをレンダリングするということはFeatureテストである Bladeってそもそも一旦PHPに置き換える必要があるので、レンダリングしてassertContains()を調べる必要がある。
    なので、例えばBladeテンプレートを展開してアイコンを表示する……などはシンプルな比較テストだけどFeatureが正しい。
    あとconfig()もFeatureテストである。PHPクラス1つのテストではなくて、config->env->最終的な値は~とやってるので横断するからFeature。

  • 一時テストが回帰テストになってる ページを削除したときなど、一度きりの検証のために書いたテストがそのまま残っているケースが結構あった。
    テスト名に_legacy_と残してくれていたので、専用の退役routeについてのfeatureテストはだいたい見抜けたが、assertDontSee()などはひと工夫。
    検索結果などを扱っているところなら比較は残し、旧テキストやボタンなどのための比較なら消す、という感じになる。

  • 「何の回帰を防いでいるテストなのか」を説明させる テストを地道に読んでみるのも重要だが……エージェントを使うという前提ならまず、肥大化したファイルは説明してもらおう。
    説明の段階でクラス名と乖離していたら分離が必要だし、説明を受けても「そのテスト必要か……?」と思ったら削除も考慮に入れる。

  • 型エラーをテストで検出しようとしている これはPHPStanなど静的解析で検出できるはずで、過剰なテストとなってしまうので不要。
    珍しい例だが1件だけなにかで入り込んでしまった。