テストと振る舞いの記述で AI コーディングを制御し、負債を増やさない

TDD と BDD で AI コーディングを制御する基本的な考え方を整理する。まず要件を確認しやすい振る舞いの記述にし、それをテストへ変換してから、AI にテストに沿って実装させる。

AI にコードを書かせると、よくある体験があります。最初は速いのに、後半になるほど乱れていく、というものです。機能の立ち上げはすぐにできますが、プロジェクトが大きくなり、修正回数が増えると、ひとつの bug を直したあとに三つの bug が出てくるような状態になりがちです。

これは完全に AI だけの問題ではありません。人間の開発者も同じような書き方をすることがあります。ただ、AI は書く速度が速いので、問題が表面化する速度も速くなります。この制御不能感を減らすには、AI に「もっと頑張らせる」のではなく、より明確な境界を与えることが重要です。まず何を正しい結果とするのかを定義し、そのうえで実装させます。

TDD と BDD は、AI コーディングの流れに組み込みやすい考え方です。TDD は「正しいかどうか」を自動テストに変えます。BDD は「これは本当に欲しい機能か」を人間が読める振る舞いの記述に変えます。両方を組み合わせると、AI の推測や自由解釈を減らし、結果を確認しやすくできます。

TDD が解決する問題

TDD は Test Driven Development、つまりテスト駆動開発です。基本的な順序は次の通りです。

  1. 先にテストを書く。
  2. テストを実行し、現時点では失敗することを確認する。
  3. 機能コードを書く。
  4. テストが通るまで実装を修正し続ける。

これは多くの人が慣れているやり方とは逆です。たとえばソート関数を書く場合、直感的には先に関数を書き、いくつか数字を入力して結果が合っているかを確認したくなります。TDD では、先に期待結果をテストとして書きます。たとえば [3, 1, 2] を入力したら [1, 2, 3] が返る、空配列を入力したら空配列が返る、重複した数字を含む配列でも正しく並ぶ、という具合です。

この意味は、開発を始める前に正しい結果が明確に定義されることです。その後、誰がコードを変更しても、テストを再実行すれば、以前合意した振る舞いを壊していないか確認できます。

なぜ以前は TDD を続けにくかったのか

TDD は聞こえはよいですが、実際のプロジェクトで継続するのは簡単ではありません。

第一に、直感に反します。空のファイルを前にすると、多くの人は先に機能を書きたくなります。特に要件がまだ曖昧なときは、テストケースを書くこと自体が難しくなります。

第二に、要件はすぐ変わります。今日まじめに書いた十数個のテストが、明日の要件変更で大きく書き直しになるかもしれません。短期的には、開発のテンポが遅く見えます。

第三に、テスト自体にもコストがあります。テストコードは自然に生えてくるものではありません。以前は、開発者が自分で書き、保守し、その価値を説明する必要がありました。短期の納期だけを見るチームでは、この作業は削られやすいものです。

しかし AI はこのコスト構造を変えました。要件をテストコードに変換する作業は、AI が得意な領域です。曖昧な説明を自由に解釈させるより、テストに沿って実装させるほうがずっと安定します。

AI にコードを書かせるときの TDD の使い方

AI に機能を書かせるときは、「この機能を実装して」ではなく、次の順序で依頼します。

  1. まず AI に要件からテストケースを列挙させる。
  2. 各テストケースに自然言語の説明を付けさせる。
  3. テストケースが実際の要件に合っているか review する。
  4. テストを確認したあとで、AI に機能を実装させる。
  5. AI にテストを実行させ、失敗結果に基づいて修正を続けさせる。

このとき、人間が主に review するのは大きな実装コードではなく、テストが要件を明確に表しているかどうかです。テストケースはたいてい「入力は何か、出力はどうあるべきか、境界条件をどう扱うか」に近いので、実装ロジックを直接読むよりかなり楽です。

たとえば AI には次のように依頼できます。

1
2
3
まだ機能を実装しないでください。
以下の要件に基づいてテストケースを書いてください。各テストケースには、カバーする業務ルールを自然言語のコメントで説明してください。
テストを確認したあとで、そのテストに基づいてコードを実装してください。

この流れは、AI が書いている途中で要件から外れる問題と、後続の修正で既存機能を壊す問題を減らせます。

TDD だけでは足りない

TDD だけでは、まだ二つの穴があります。

一つ目は、テストがすべて通っても、プロダクトが本当に期待通りとは限らないことです。テストは、コードがテストに書かれたルールを満たしていることしか証明しません。テストそのものがユーザーの要求を正しく表現していなければ、コードは「正しく間違ったこと」をしてしまいます。

二つ目は、テストコードが非エンジニアにとってまだ読みやすいものではないことです。自然言語のコメントがあっても、多くの人は大量のユニットテストを読みたがりません。要件がプロダクト体験寄りになるほど、テストコードだけで「これは自分が欲しかったものか」を確認するのは難しくなります。

そこで BDD が必要になります。

BDD が解決する問題

BDD は Behavior Driven Development、つまり振る舞い駆動開発です。コード内部をどう書くかではなく、ある場面でシステムがどのように振る舞うべきかに注目します。

BDD ではよく Given / When / Then という形式を使います。

  • Given:ある前提状態。
  • When:ユーザーまたはシステムが行う操作。
  • Then:期待される結果。

たとえば吸血効果を持つゲームキャラクターは、次のように記述できます。

1
2
3
4
5
Given 盤面に、残り HP が 1、攻撃力が 2、最大 HP が 5 の吸血鬼がいる
And 隣接マスに、残り HP が 10 の敵ユニットがいる
When 吸血鬼がその敵ユニットを攻撃する
Then 敵ユニットの残り HP は 8 になる
And 吸血鬼の HP は 3 まで回復する

これはコードではありませんが、「敵を攻撃したときに生命値を回復する」よりずっと正確です。初期状態、操作、結果が書かれていますし、あとで補うべき問題も見えてきます。敵の HP が 1 しかない場合、吸血鬼は実際に与えたダメージ分だけ回復するのか、それとも攻撃力分回復するのか。吸血鬼がすでに最大 HP の場合、超過分の回復はどう扱うのか。

こうした問いが早く出てくるほど、あとで AI が勝手に推測する余地は減ります。

なぜ BDD は AI と相性がよいのか

BDD も以前は導入コストが低くありませんでした。プロダクト、開発、テストが同じ振る舞いの記述でコミュニケーションする必要があるからです。しかし現実には、そのような協作習慣を持たないチームも多いです。

AI 時代には、BDD のコストが下がります。まず次のような粗い要件を一文で書くだけで十分です。

1
吸血鬼が敵を攻撃したあと、与えたダメージと同じ量の HP を回復する。

そのうえで、AI に Given / When / Then のシナリオを生成させます。うまく動く AI なら、境界条件を追加し、不明確なルールを質問してきます。人間がやるべきことは、実装コードを直接読むことではなく、その振る舞いの記述を確認することです。

振る舞いの記述が明確になったら、AI にそれをテストコードへ変換させ、最後にテストに基づいて機能を実装させます。この流れはかなりスムーズです。

より安定した AI コーディングフロー

実際には、BDD と TDD をつなげて使えます。

  1. まず自然言語で要件を書く。
  2. AI に BDD の振る舞いシナリオへ変換させる。
  3. 人間が Given / When / Then が期待通りか確認する。
  4. AI に振る舞いシナリオを自動テストへ変換させる。
  5. 人間がテストのカバー範囲を素早く review する。
  6. AI に機能を実装させる。
  7. テストを実行し、失敗したら AI にエラーに基づいて修正させる。
  8. 最後に人間が受け入れ確認とコード review を行う。

ここで重要なのは順序です。最初から AI に完全な実装を書かせるのではなく、まず要件を確認可能な振る舞いに変え、次に実行可能なテストに変えます。こうすると、AI が自由に解釈できる余地はかなり小さくなります。

次のようなプロンプトをそのまま使えます。

1
2
3
4
5
6
7
この要件を BDD + TDD の流れで処理してください。

ステップ1:まず要件を Given / When / Then の振る舞いシナリオに整理してください。コードは書かないでください。
ステップ2:不明確なルールを列挙し、私に確認してください。
ステップ3:振る舞いシナリオが確認されたあとで、それらをテストケースに変換してください。
ステップ4:テストが確認されたあとで、機能を実装してください。
ステップ5:テストを実行し、失敗結果に基づいて修正し、すべてのテストが通るまで続けてください。

この種のプロンプトは複雑ではありませんが、AI の働き方をはっきり変えます。いきなり完成しているように見えるが検証しにくいコードを書くのではなく、先に要件を絞り込み、その後で実装に入るようになります。

優先して使いたい場面

BDD + TDD はすべてのタスクに必要なわけではありません。一回限りのスクリプト、一時的なデータ処理、小さなスタイル調整では、完全な流れは重すぎるかもしれません。

より向いているのは次のような場面です。

  • 業務ルールが多く、誤解しやすい。
  • 境界条件が多く、今後も継続的に変更される。
  • ゲーム、課金、権限、状態機械、フォームバリデーションなど、ロジックが濃い機能。
  • 複数人で要件を確認する必要がある。
  • コードを長期保守する予定で、一度生成して終わりではない。
  • すでに「AI が修正するほど乱れていく」状態が出ているプロジェクト。

AI にボタン文言をひとつ変えさせるだけなら、完全な流れは不要です。しかしキャラクタースキルシステム、注文状態の遷移、権限判定、ポイントルールなどを作るなら、先に振る舞いシナリオとテストを書くほうが割に合います。

使うときの注意点

第一に、テストは多ければよいわけではありません。テストは重要なルールと高リスクな境界をカバーすべきで、実装の細部をすべて固定するものではありません。そうしないと、少しの要件変更でもテストが保守負担になります。

第二に、BDD シナリオは具体的に書く必要があります。「システムは正常に動作するべき」「体験は滑らかであるべき」のような検証できない記述は避けます。どの状態で、何が起き、結果がどうなるべきかを明確に書きます。

第三に、人間の review はまだ必要です。AI はテストや振る舞いシナリオを生成できますが、あなたが本当に望むプロダクト上の取捨選択までは知りません。特に境界ルールは、人間が確認する必要があります。

第四に、テストが通ったあとも、実際に機能を動かす必要があります。自動テストはロジックの問題を受け止められますが、UI 体験、性能、インタラクションの細部、ユーザー感覚は人間の受け入れ確認が必要です。

まとめ

AI はコードを書くのが速いですが、速さは安定性と同じではありません。要件が複雑になるほど、「これを実装して」という一文だけに頼るべきではありません。よりよい方法は、要件を確認可能な振る舞いに分解し、その振る舞いを実行可能なテストに変え、最後に AI にテストに沿ってコードを実装させることです。

TDD は AI に何を正しい結果とするかを伝えます。BDD は人間が、その機能が本当に欲しかったものかを確認しやすくします。両者を組み合わせる目的は儀式を増やすことではありません。AI の推測空間を減らし、「速く書く」を「安定して変更する」に変えることです。

记录并分享
Hugo で構築されています。
テーマ StackJimmy によって設計されています。