tests-and-ai-coding-tools.html
< BACK 擦り減ったデスク、コードが表示されたノートパソコンを開き、ロンドンの窓からの涼しい光の下でお茶のカップが置かれている。

なぜ私はまたテストを書き始めたのか(AIが私にそうさせた)

2022年初頭、私は静かに決断を下した。Seahawkを通じてやってくるWordPressとNodeプロジェクトのほとんどでユニットテストを書くのをやめたのだ。派手に。ブログ投稿もなしだ。ただやめたのだ。その根拠は健全だと思っていた。月に15〜20件のクライアントサイトをリリースしていて、3人の開発者をローテーションで抱えていて、書いていたテストは誰も読まないドキュメントに見えた。手動QAが本当のバグを見つけていた。テストは舞台装置だった。WordPress and Node projects coming through Seahawk. Not loudly. No blog post about it. I just... stopped. The justification was sound, I thought -- we were shipping 15 to 20 client sites a month, I had three other developers on rotation, and the tests I was writing felt like documentation nobody read. Manual QA caught the real bugs. Tests were theatre.

2023年後半へ進む。GitHub Copilotはエディタに約8ヶ月いた。私はCursorも同時に、グリーンフィールドなら何でも使っていた。スピードは本当に驚異的だった。だが何か起き始めた。触っていない場所にバグが現れ始めた。正しく見えるロジックが、チェックしたことのないエッジケースで間違っていた。最悪なことに、AIはそれが間違っていることに気づいていない。いつもと同じ自信のあるインデントで壊れたコードを書いた。Cursor on the side for anything greenfield. The speed was genuinely remarkable. But something started happening. Bugs were appearing in places I hadn't touched. Logic that looked correct was wrong in edge cases I'd never thought to check. And the worst part -- the AI had no idea it was wrong. It wrote the broken code with the same confident indentation it always does.

その時が、私がテストを再び手に取った時だ。

---

テストを放棄した時期(そしてなぜそれが当時意味があったのか)

正直に答えると、特定のタイプのプロジェクトではテストをスキップするのが正解だった。5ページのブロシュアサイトをWordPressで構築しているなら、お問い合わせフォームプラグインのPHPUnitテストを書くのはパフォーマンス的な無駄だ。それは今でも変わらない。was the right call. If you're building a five-page brochure site in WordPress, writing PHPUnit tests for a contact form plugin is theatre. I stand by that.

Seahawkの長年の本業は、まさにそういう仕事だった。高容量、相対的に低い複雑さ、明確なスコープだ。クライアントがFigmaファイルを渡す。お前がビルドして、QAして、リリースする。フィードバックループは短い。何か壊れたら、数時間以内にわかる。そういう文脈でテストを書くのは、付箋をラミネートするのと同じだ。

だが私はそのレッスンを無謀に一般化した。すべてのプロジェクトをブロシュアサイトのように扱い始めた。カスタムWooCommerceチェックアウトフローのある奴ら。2023年初頭にフランクフルトのクライアント向けに構築したフィンテックダッシュボード、フルカスタムREST API、JWT認証、3つの異なるユーザー権限レベル。テストなし。「慎重な手動QA」だけだ。それは傲慢だったし、私たちに降りかかってきた。all projects like brochure sites. Even the ones with custom WooCommerce checkout flows. Even the fintech dashboard we built in early 2023 for a client in Frankfurt -- full custom REST API, JWT auth, three different user permission tiers. No tests. Just "careful manual QA." That was arrogant, and it bit us.

フランクフルトのプロジェクトは権限バグを抱えたままリリースされた。編集者レベルのユーザーが特定のフィルター組み合わせで管理者レベルのデータにクエリできるバグだ。彼ら内部のセキュリティレビューを実施して初めて気づいた——本番環境デプロイから6週間後。恥ずかしい。修正可能だ。だが基本的なインテグレーションテストなら、プルリクエストを立てる前に引っかかったはずだ。

---

AI コーディングツールが実際に変えたこと

Copilotとか Cursorとか、その月ホットなモデルについて話すとき、ほとんどの人が見落としていることがある——コードが正しく見える。それが問題だ。looks right. That's the problem.

ジュニア開発者がバグのあるコードを書くと、そのぎこちなさが見えることが多い。奇妙な変数名、「// ここ自信ない」というコメント、明らかに2回コピペされた関数。コードはそれ自体の脆さを表に出す。AIコードはそうではない。スタイル的に一貫していて、命名も適切で、意図的に見える構造をしている。自信は完全に表面的なものだ。// not sure about this, a function that's clearly copy-pasted twice. The code telegraphs its own fragility. AI code doesn't. It's stylistically consistent, well-named, and structured in a way that reads as intentional. The confidence is entirely cosmetic.

スタンフォード大学のHuman-Computer Interactionグループの研究によると、AI助言ツールを使う開発者は、生成されたコードを最初の一読で過度に信頼する傾向にあるという。それは私の経験とも一致する。Copilotが書いた40行の関数をさっと見て、「まあ、自分が書いたものと大体同じだ」と思って進めていた。時には大丈夫だった。時には微妙に私の実際の要件を誤解していた。 have flagged that developers using AI assistants tend to over-trust generated code on first read. That tracks with my own experience. I would glance at a 40-line function Copilot had written, think "yeah, that's basically what I'd have written," and move on. Sometimes it was fine. Sometimes it had silently misunderstood what I actually needed.

何度も引っかかった具体的な失敗モード——AIが予想する理由のなかった境界ケースの条件分岐だ。幸福なパスは完璧に扱うのに、nullの入力、空の配列、非標準の日付フォーマットで静かに失敗する関数を書く。自分でコードを書いていれば30秒で考えられたことだ。入力しながら考えていただろうから。conditional logic around edge cases the AI had no reason to anticipate. It would write a function that handled the happy path perfectly and then quietly fail on null inputs, empty arrays, or non-standard date formats. Things that would have taken me thirty seconds to think about if I'd written the code myself, because I'd have been thinking as I typed.

スピードの罠

ここには本当の生産性の罠がある。AIはあなたを速くする。速さは良く感じる。より速く出荷し始め、速度が品質の証拠のように感じられるため、レビューはより注意深くなくなる。そうではない。言語モデルにプロンプトを与えるとき、スピードと正確性は相関していない。

昨年9月、AIの支援なしでは実現できなかったよりも、大体40%多くの機能をクライアントプロジェクトに組み込んだ。そのプロジェクトは、ここ2年間で出荷した他のどのプロジェクトよりも、ローンチ後のバグが多かった。致命的なバグではない。ただ厄介なバグだ。クライアントの信頼を損なわせるようなやつだ。

---

テストが今(AIがループに入った場合)異なる理由

テスト記述に戻ってきたとき、古いワークフローに戻らなかった。テスト最初、それから実装、それからAI支援のコードレビュー。今、私が落ち着いたループがそれだ。

興味深いのは、AIは実は、アプリケーションロジック執筆ではないときのように、テスト執筆で非常に優れているということだ。Copilotに定義されたよい関数シグネチャを与えて、テストスイートを生成するよう依頼すれば、手動で書くのに20分かかるであろうエッジケースカバレッジを生成する。タスクが具体的に「これがどのように壊れるのかを見つけること」であるとき、それは不幸なパスをよく想像する。AI is actually excellent at writing tests, in a way it isn't always excellent at writing application logic. Give Copilot a well-defined function signature and ask it to generate a test suite and it'll produce edge-case coverage I'd have taken twenty minutes to write manually. It imagines unhappy paths well when the task is specifically "find ways this can break."

だからある種、逆にした。テストスペックを書く。AIがテストケースを埋める。その後AIが実装を書く。その後、コードを冷たく読むのではなく、それらのテストのレンズを通して実装を読む。through the lens of those tests, rather than just reading the code cold.

これは純粋なノリでコーディングするより遅い。ただし、テストを含むすべてを手動で書く昔のワークフローより速い。そしてフランクフルト以来、ゼロの権限バグを出荷した。

実際に使っているツール

  • JavaScriptまたはTypeScriptなら[Vitest](https://vitest.dev)。去年Jestを完全に置き換えた。設定がまともで、ウォッチモードが速い。 for anything JavaScript or TypeScript. Replaced Jest for me entirely last year -- the config is saner and the watch mode is quick.
  • WordPress とカスタム PHP の仕事は PHPUnit を使い続けている。これに取って代わるものはまだない。 still, for WordPress and custom PHP work. Nothing has replaced it.
  • Cursorの「この関数をテストして」ショートカット。本当に、使ったことのあるどのエディタでも最も有用な単一機能の一つだ。 -- genuinely one of the most useful single features in any editor I've used.
  • CI には GitHub Actions を使う。main へのプッシュごとにテストが実行される。ほとんどのプロジェクトで約 90 秒かかる。 for CI. Tests run on every push to main. Takes about 90 seconds on most projects.

---

テストに対する議論(公正に論じた場合)

この立場に真摯に向き合いたい。なぜなら、ほぼ 2 年間、私自身がこの立場にいたからだ。

本当の議論は「テストは無用だ」ではない。「テストにはコストがあり、多くのプロジェクトはそのコストを正当化しない」だ。テストスイートを書いて保守するのに時間がかかる。短命プロジェクト、キャンペーンマイクロサイト、マーケティングランディングページ、ハッカソンプロトタイプなら、その時間投資はゼロリターンだ。プロジェクトはテストが何かを救う前に死ぬ。

もっと微妙な点もある — 不十分なテストは、テストがないよりも悪い。テストが同義反復になっているために合格するテストスイート(本質的に、関数が返すべき値を返しているかをテストしているだけ)は、根拠のない自信を生む。代理店でこれを見かけた。開発者が、実際に何を検証しているのかを誰も問い直さないから、いつも合格するテストを書いているのだ。bad tests are worse than no tests. A test suite that passes because the tests are tautological (you're essentially testing that your function returns what you told it to return) gives you false confidence. I've seen this at agencies. Developers writing tests that always pass because nobody challenged what they were actually verifying.

Martin Fowlerはこれについてよく書いている。カバレッジのパーセンテージはテスト品質の尺度ではない。90%カバレッジ数は、完全に虚ろなスイートを隠すことができる。 -- coverage percentages are not a measure of test quality. A 90% coverage number can mask a completely hollow suite.

つまり、すべてをテストするな。プロフェッショナルに見えるからテストするな。負荷を支える論理を特定して、それが壊れると高くつく場合にテストしろ。

---

今、私がテストすること(そしてテストしないこと)

ここに、過去8~9ヶ月間で辿り着いた実際の判断がある:

私はテストする:

  1. お金、権限、またはデータ変換を扱う関数すべて
  2. 単純なCRUDパススルーではないAPIエンドポイントすべて
  3. クライアントが書面で正確な動作を指定したカスタムビジネスロジック
  4. AIが書いたが、1行ずつ完全には読まなかったすべてのコード

私はテストしない:

  • UIレンダリング(スナップショットテストは9年間で一度も役に立ったことがない。一度も。)
  • 外部の動作が自分のコントロール下にないサードパーティAPIラッパー
  • 一度だけ実行して削除される使い捨てスクリプト
  • 標準的なWordPressフック(ただし何か変わったことをしている場合は除く)

それだけだ。大げさな哲学はない。実際に失敗を経験した場所に基づいたリストにすぎない。

---

実際に機能するワークフロー

参加しているSlackコミュニティで何人かに聞かれたから、実際の流れを紹介する。

  1. ファイルの最上部に簡潔なスペック コメントを記述する -- このモジュールが何をするのか、何をしないのか、既に認識している エッジケースについて。
  2. 実装を書く前に、Cursorにそのコメントからテストケースを生成するよう依頼する。
  3. テストケースを確認する。つまらないやつは削除する。AIが見落としたものを追加する。
  4. CopilotやCursorに実装を書かせる。
  5. テストを実行する。失敗する。実装を修正する(テストではなく)。
  6. プッシュ前に差分を確認する -- AI支援コードであっても人間によるレビューが必要だ。

ステップ6は譲歩の余地がない。過去4ヶ月間、diffをじっくり読んでからプッシュするだけで、本当に悪いバグを3つ捕捉している。難しいことではない。ただ読むだけだ。

Kent Beckが当初提唱したTDDのフレーミングは、決して100%のカバレッジや完璧な方法論についてではなかった。それは、ミスが複合する前に捕捉できるくらい高速なフィードバックループを構築することだった。そのアイデア -- 高速なフィードバックループ -- は2003年当時よりも今のほうがはるかに関連性がある。AIは私がこれまで雇用した開発者のいかなるものよりも高速にミスを犯すからだ。 was never about 100% coverage or perfect methodology. It was about building a feedback loop fast enough to catch mistakes before they compound. That idea -- fast feedback loops -- is more relevant now than it was in 2003. Because the AI makes mistakes faster than any developer I've ever hired.

---

FAQ

納期スピードは遅くなるか?

複雑なプロジェクトであれば10%から15%程度。単純なものであれば、まったくないかもしれない -- AIはテストを非常に迅速に生成するため、オーバーヘッドは最小限だ。バグが修正に実際のお金がかかるプロジェクト(ほとんどの実金銭プロジェクトがそれに該当する)では、その15%は百倍の価値がある。

TypeScriptはどうか。強い型付けによって、多くのテストが不要になるのではないか。

部分的にはそうだ。TypeScriptはコンパイル時に、以前はテストが必要だった一連のエラーをキャッチする。しかし型はビジネスロジックをテストしない。割引計算関数が卸売顧客に対して正しいルールを適用しているかどうかを検証しない。それはあなたの責任だ。

テストを書いていないジュニアデベロッパーがAIコーディングツールを使うべきか。

いいえ。強い意見だ。テストなしでCopilotを使うジュニアデベロッパーは、本質的にオートパイロットで飛行機を操縦していることと同じだ。オートパイロットの仕組みやマニュアルで着陸する方法を理解せずに。AIは上級レベルに見えるコードを生成し、ジュニアはどの部分を信用できないかわからず、結局本番環境でインシデントが発生する。テストは少なくとも、受け入れているアウトプットを検証するためのメカニズムを与えてくれる。

そもそも、なぜテストをやめたのか。正直に答えてほしい。

燃え尽き症候群、部分的にはそうだ。そして、すべてのプロジェクトが本当に単純で、テストが本当に価値を生み出していない期間があった。間違いは、プロジェクトの複雑さが変わったときに気づかず、それに応じて調整しなかったことだ。本当の教訓は「常にテストする」でも「テストしない」でもなく、与えられたプロジェクトがどのカテゴリーに属するのかを知ることだ。

---

テストを書くことは、かつて保護のように感じなかった。書類仕事のように感じた。AIがそれを変えた。AIが悪いからではなく -- 実際にスピードが上がった -- むしろ、自信があり、整形式で、見た目は妥当に見えるが、私が従来の方法でコードを読んで捕捉できないミスの新しいクラスが導入されたからだ。テストはAIのためのものではない。私のためのものだ。モデルがどんなものを渡してきても受け入れる前に、私が実際にコードに何をさせる必要があるかについて考えるための強制的な関数だ。

2年前にそのように考えていればよかったと思う。

< BACK