20.2K Views
September 28, 24
スライド概要
XP祭り2024での登壇資料です。
https://confengine.com/conferences/xp2024)
生成AI時代の テスト駆動開発 XP祭り2024 Sep. 28, 2024 Takeshi Yonekubo
About Me • 米久保 剛 (よねくぼ たけし) • SIer勤務のアーキテクト • X: @tyonekubo • 『アーキテクトの教科書 価値を生むソフトウェアのアーキテクチャ構築』
アジェンダ I. テスト駆動開発の現状と課題 II. コード生成AI概論 III. コード生成AIの実験 (1) IV. コード生成AIの実験 (2)
I. テスト駆動開発の現状と課題
TDDは死んだ? • DHH氏の記事が話題となったのが10年前 • 日本においては、生死を問うほどTDDは普及していな かったし、現状も大きくは変わっていない • いまだに「テストファースト」と「TDD」が混同して 使われがちなことや、有用性の評価以前にチャレンジ に至らないことはもったいなく感じる https://dhh.dk/2014/tdd-is-dead-long-live-testing.html
テストファースト/テスト駆動開発の壁 アウトサイドイン のテスト駆動開発 テスト駆動開発 テストファースト テストコードを 書く (本日は触れない) ← 第二の壁 ← 第一の壁 サービス開発、プロダクト開発では一般的に普及 テストコードを 書かない ※必ずしも上のレベルに到達すべきという意図はなく、実践の難易度を表現した図
第一の壁:テストファースト テストを先に書かない/書けない理由: • 面倒くさい、早くプロダクションコードを書きたい • 振る舞いの単位を(事前に)識別できない
振る舞いの単位の識別 • 多くの場合、複数の小さなコンポーネントの協調に よって振る舞いが提供される • 振る舞いの単位を事前に適切に識別できないと、テス トコードを先に書くことは難しい DOC テストコード SUT DOC 振る舞い SUT (System Under Test) : テスト対象 DOC (Depended-on Component) : SUTが依存するもの DOC (Test double)
テストコードを後から書くデメリット プロダクションコードを書いた後にテストコードを書く のは、ある意味楽ではあるが、デメリットがある • テスト容易性が低くなる傾向 • エッジケース的な振る舞いに後から気づくことで、大 きな手戻りが発生するリスク
テストファースト • 大きな振る舞いを構成する小さな振る舞いの単位を識 別し、コンポーネント分割を行う事前設計が必要 • 過度な設計、オーバーエンジニアリングのリスクは内 在する 振る舞い テスト コード 振る舞い テスト コード
第二の壁:テスト駆動開発 やり方は極めてシンプル (Red – Green – Refactor) だが、実践するのはそう簡単ではない
TDDの難しさ 「シンプルなテストケースから始めて、Red-GreenRefactorのサイクルを繰り返し、少しずつ設計を進化さ せる」とは具体的にどうすれば良いのか? • ステップバイステップで進めたからといって、良い設 計が無から出現するわけではない
仮説としての事前設計 • 解決すべき問題(=振る舞いを実現する)に対して、 仮説としてのラフな設計はある程度行う • 人間の認知の仕組み上、無の状態から始めるのは難し く、知識や過去の経験によって構築されたスキームや メンタルモデルにより、解決策の候補は想起される • ただしそれが、目の前にある具体的な(特殊な)問題 に対する最適解である保証はない
TDDの要点 • 大きくて複雑なタスクを、十分に小さくシンプルなサ ブタスクに分割することで、知識やパターンを正しく 選択して適用できるようになる • リファクタリングによって、より良い選択に変更する チャンスがある • 仮説に固執せず、Just In Timeで柔軟な設計を行う
例)アンクルボブのボウリングスコア集計 • 『アジャイルソフトウェア開発の奥義』の 第6章「プログラミングエピソード」 • ボウリングのスコア集計アプリケーションをTDDで開 発する例
例)アンクルボブのボウリングスコア集計 TDDで生まれた設計は事前設計から大きく変わった。 “このscoreForFrame関数を見てごらんよ! これこそボ ウリングのルールをきわめて簡潔に記述した姿だよ!”
TDDの習得方法 • ひたすら練習あるのみ • 「事前に想定していた仮の設計より、TDDの結果生ま れた設計の方がシンプルですぐれていた」という成功 体験を重ねると、TDDがデフォルトになる • TDDの呼吸を身に付ける
II. コード生成AI概論
コード生成AIは群雄割拠時代 チャットサービスやAIコードエディタなどさまざまな サービス、ツールが登場し、しのぎを削る • v0 by Vercel • Claude Artifacts • Amazon Q • GitHub Copilot • Cursor etc..
プログラマーは不要になる?
思考実験:プログラマーが不要となった未来 • ソフトウェアは完全にブラックボックス化される • 仕様書やテストの大半をAIが生成し、人が最終チェック 仕様書 ユーザー エンジニア </ テスト > ソフトウェア (Black box)
これが可能となるのは数世代先の AI(LLMではない、何か) ※LLM:大規模言語モデル(Large Language Model)
当面は続くLLM時代 ⇒LLMの仕組み、能力、限界を 把握し、LLMを最大限に活用
LLMの(ざっくりとした)理解 利用者としては、詳細なメカニズム(Attention機構や Transformer)に立ち入らず、要はLLMとはこんなもの というメンタルモデルを構築しておけばOK https://poloclub.github.io/transformer-explainer/
LLMのメンタルモデル • LLMは「大量学習データをもとに、与えられた文章の 続きを確率的に予測し、もっともらしい文章を完成さ せるマシン」 • ランダム性を有しガチャ要素はあるが、指示の与え方 (入力する文章)を工夫することで期待する回答を得 る可能性を上げられる(=プロンプトエンジニアリン グ)
LLMとプログラミング • LLMの学習済みデータには、大量のソースコードや技 術文書が含まれるため、LLMはプログラミングに関す る知識を豊富に持っている • メジャーな言語やフレームワークの方が多くの知識を 学習しているため、得意である • 設計や実装についても、特殊な問題よりも一般的な問 題を解く方が得意である(特殊な問題には、より高度 な推論能力が必要。OpenAI o1に期待?)
実験 Claude 3.5 Sonnet に以下の推論問題を与える。
結果:正しく推論
コード生成AI • LLMの持つプログラミング能力を活用し、ユーザーの コーディング活動を支援することを目的としたAIシス テム • 単にチャットで回答するだけでなく、自律的に行動す るAIエージェントやそれを組み込んだAIエディタを使 うことで開発生産性の向上が可能(GitHub Copilot や Cursor) • とくにCursorは開発者体験が格段に優れている
III. コード生成AIの実験 (1)
実験の趣旨 • 「コード生成AIでXXアプリが一発で作成できた!」 →ガチャを引き続ければそれなりのものは生成できる • 一方で、正規表現やらシンプルな関数などは、LLMの 能力的にできて当たり前 われわれの関心事は「複雑なプログラムを、プロダク ション環境にデプロイ可能なレベルの品質で作ることが、 コード生成AIによってどの程度できるのか」 および「そのための具体的な手法、テクニック」 →ある程度複雑な題材で実験してみた具体例を紹介 ※以降の実験は全てCursor (Pro) + Claude 3.5 Sonnetを利用して行った
題材(1) each関数 • Jestでパラメーター化テストの記述に利用する、 test.each関数を模した関数を作成する • JavaScriptのタグ付きテンプレートリテラルを利用
試行1 テストファースト〜一発生成(NG) • テストスイートを示 し、全てをパスする コードの実装を指示 • 数回ガチャを回した が、一発で正解を出 すことはできなかっ た • 問題が複雑過ぎるた めに分解が必要と推 測
試行2 テスト駆動開発 デモ動画
考察 • 人間がテストコードを書き、AIがそれをパスする実装 コードを書くピンポンプログラミングによるTDDはう まくいった • 一発で正解を導くことができなかった複雑な問題に対 しても、分割して段階的に取り組むことで正解を導く ことができた • 段階的に進めることで、AIが生成したコードの理解や レビューもしやすくなる(必要に応じてリファクタリ ングのステップも盛り込むことも可能)
IV. コード生成AIの実験 (2)
題材(2) コード行数カウンター • ディレクトリ配下のソースコードの行数をカウントす るJavaScriptのCUIプログラムの生成 • 言語ごとにコード行数、コメント行数、空行数、トー タル行数をカウントして表形式で結果を出力する
プログラム仕様書 • 仕様書はマークダウン形 式で記述し、AIにファイ ル参照させる
Cursor Composer • 複数のファイルをまとめて生成したり、編集したりす ることができる機能 • Beta版機能(Pro以上で有効化することで利用可能)
試行1 一発生成 • 一発で動作するコードが生成される場合と、うまくい かない場合とがあった • 動作しない場合は修正を試みるよりやり直した方が早 い(ガチャ)
試行2 部品化&テストコード生成 • 保守性、テスト容易性を考慮したプログラム分割と、 テストコード生成を指示
試行2 部品化&テストコード生成 • プログラムは正しく動作したものの、テストコードは 失敗する状態
試行2 部品化&テストコード生成 • Debug with AI機能で修正を試みるもうまくいかず • モックの多用によりテストコード読解が困難で、マ ニュアルでの修正も諦めた
試行3 部品化&テストコード改善 • テスト容易性に関する具体的な制約を追加 • 「テストコードでスタブやモックを多用しなくて済む ように設計する」
試行3 部品化&テストコード改善 • プログラムは正しく動作し、テストも全てパス
試行3 部品化&テストコード改善 試行2で生成されたコードとの比較: • 個々の関数内で設定ファイルを読み込むのではなく、 読み込んだ設定内容をメイン関数から引数で渡すよう になった • ディレクトリやファイルへのアクセスは、引き続き モックによる検証となっていた 全体的には、コードの質はさほど改善されなかった。
試行4 テストファースト • 段階的な実装を指示(個々の関数は空実装から開始)
試行4 テストファースト • データの流れと処理を説明させるステップを追加 →具体例を、LLMに入力するコンテキストに含めるこ とで精度を上げる狙い
試行4 テストファースト • 個々の関数毎に、必要なテストケースを検討させ、実 際にテストコードを生成させる
試行4 テストファースト • テストコードをパスする関数の実装を指示する • この手順を、関数の数だけ繰り返す
試行4 テストファースト • テストが失敗する場合もあるが、処理が小さく分割さ れておりコードの可読性も高いため、「Debug with AI」機能で比較的容易に修正できた
試行4 テストファースト • 正しく動作するプログラムが完成し、テストケース数 も増加した
各試行の比較 試行1 一発生成 試行2 部品化&テスト 試行3 部品化&テスト改 試行4 テストファースト プロダクション コードの可読性 低い 普通 普通 普通 テストケース数 N/A 3ケース 5ケース 23ケース テストコードの可 読性 N/A 低い 普通 高い (モック未使用) ※可読性の判定は筆者の主観による ※実際のコードはGitHubリポジトリを参照 https://github.com/yonetty/xp-matsuri-gen-ai-tdd
考察 N=1ではあるが、実験結果から以下の仮説を持っており、 体感としてはおそらく間違ってはいない • 複雑なプログラムを一発で生成させることはガチャ要 素が大きい上、コードの品質も安定しないので避けた 方がよい(使い捨てのスクリプトや、プロトタイプの ような用途ならOK) • 処理を分割した上で、個々にテストファーストで進め た方が、テストコード、プロダクションコードともに 内部品質は向上する
まとめ
①分割統治の有効性 人間であれ生成AI(LLM)であれ、 複雑な問題をシンプルで小さな問題に 分割することで、一般解としての知識 やパターン適用をしやすくなる。
②生成AIエンジニアリング 実務で生成AIを活用するならば、 ガチャとならないようにLLMの特性を 把握してエンジニアリングを行う。 生成AIを用いる場合でも、テストファー ストやテスト駆動開発の手法は有効。
③副操縦士としての生成AI 開発するプログラムの複雑度やその他の 特性に応じて、人間の介在度合いを制御 する。現時点では、完全に生成AIに任せ ることは難しく、Copilot(副操縦士)の 役割が妥当である。
ご拝聴ありがとうございました fin