この論文は、テスト駆動開発を修正して大規模言語モデルを取り込んだ、ヒューマン・イン・ザ・ループのコード生成プロセスである LLM4TDD を探究する。その前提は、テストコードは実装コードよりも概念的に書きやすいというものである。テストは状態をセットアップし、メソッドを実行し、実際の出力と期待される出力を比較するだけでよいのに対し、実装は仕様を満たすロジックを捉えなければならない。LLM4TDD では、開発者がユニットテストを書き、LLM がそのテストが通るようにコードを生成し、続いて開発者が次のテストを与え、このサイクルを繰り返す。著者らは、これによって開発者が実装に対する一定のオーナーシップを保てること、そして開発者が各テストに応じてコードが段階的に変化していく様子を見守るため、ブラックボックスなプログラム合成が直面する信頼の問題を避けられる可能性があることを論じている。

ワークフローでは、開発者は問題とテストスイートを選び、コード実行環境をセットアップし、最初のテストと TDD の原則に従うよう指示を含むプロンプトを LLM に与える。生成されたコードはそれまでに集めたテストに対して実行され、いずれかが失敗すると、内側のループで失敗したテストを報告するプロンプトが LLM に送られる。LLM が同じコードを繰り返し続ける場合には、段階的に踏み込んだヒントのプロンプトが続く。まずコードが変わっていないことを指摘し、次に使用すべきデータ構造を提案する。スイート内のすべてのテストが通った時点でプロセスは終了する。著者らはこれを ChatGPT、Visual Studio Code、そして Python による LeetCode の 70 問(easy 25 問、medium 24 問、hard 21 問)で評価し、最終的なコードを正解基準としての LeetCode 自身のテストスイートと照合している。

要点

  • LLM4TDD は 70 問中 62 問(88.5%)で LeetCode のテストスイートを通過するコードを生成した。失敗した 8 問のうち、3 問は著者らの手動テストには通ったが LeetCode のテストには通らず、5 問はコードを繰り返すところで行き詰まった。
  • テスト数とプロンプト数の平均比は、理想的な 1:1 ではなく 5:8 だった。著者らはその理由を 2 つ観察している。27 問(38.6%)では ChatGPT がわずかな変更だけを加えた同じ誤ったコードを提案し続け、23 問(32.9%)では新しいテストを通すための変更が、それまで通っていたテストを壊した。
  • ChatGPT は、テストがそれと矛盾する場合でも、説明的な関数名から推測を行った。そのため著者らは、説明的なテスト名は保ちつつ、関数名は汎用的な形にサニタイズすることを推奨している。
  • 入出力のペアが複数の異なる関数に当てはまりうるテストは、ChatGPT を誤った推測に導いた。著者らは明確で曖昧さのないテストを推奨し、整数を扱う問題は意図せず曖昧なテストになりやすいとして特に挙げている。
  • 入力空間分割に基づいて手動で書かれたテストスイートは、Pynguin ツールで自動生成されたスイートよりも少ないプロンプトで済んだ。後者は平均で 2 倍のプロンプトを要し、hard の問題では 2.5 倍に増えた。
  • テストをテストコードではなく平文で記述すると平均で 2.0 倍のプロンプトを要し、テストを複数のアサートを含む 1 つのメタテストに統合すると 1.5 倍を要したうえ、以前のテストが壊れるのを防ぐこともできなかった。著者らはどちらも推奨していない。
  • 真偽値を返す問題が最も少ないプロンプトで済み、文字列を入力とする問題は整数を入力とする問題よりも少ないプロンプトで済んだ。

注記

この研究は単一の LLM(ChatGPT)と関数レベルの LeetCode の問題を用いており、著者らは今後の課題として Codex などの他の LLM や Java などの他の言語を挙げている。また、ChatGPT が繰り返した誤ったコードはオンラインに投稿された誤った解答とわずかに似ている程度であったこと、そしてその性能は問題が学習データのカットオフより前のものかどうかに依存していないように見えたことも報告している。問題、テストスイート、プロンプトのデータセットは著者らによって公開されている。