どうもTORAです。
AI駆動開発で何ヶ月か回してきたプロジェクトが、結合テストに入りました。製造と単体テストはもう終わっていて、CIは毎日グリーン、テストエラーもなし、カバレッジも100%。それなりに仕組みを作り込んでやってきたので、単体テストまでの工程については結構うまく回せたと思っていました。
で、結合テストに入って最初に出た障害のひとつが、Repositoryのマッピングエラーでした。SQLでSELECTしているカラムに対応するフィールドが、JavaのEntity側にない。
これ見た瞬間に「あれ?」と思ったんですよね。NULLの扱いとか、特定のデータのときだけ起きるとか、そういう話じゃない。単純にSQLとEntityの定義がズレてる。こういうのって、Repositoryのテストを一回でも動かしてたら、その時点で落ちるはずなんです。
なので最初は正直、「これ誰かテストやってないんじゃないか? なめんなよ? お?」と思いました。まあ、実際そういうことはありますからね。
調べてみたら、本当にそのRepositoryのテストコードがありませんでした。ただ、そこで今度は別の「あれ?」が出てきます。単体テストが終わった時点でカバレッジ100%は確認しているし、CIもずっとグリーンで、テストエラーも出ていない。なのにテストがない。
100%って数字を見ると、安心するんですよ
少し考えて、ようやく分かりました。RepositoryはJava上ではほぼインターフェースなので、テストがなくてもカバレッジ上は穴として見えないんです。
カバレッジ100%も、CIがグリーンなのも、テストエラーがゼロなのも、数字としては何も嘘をついていない。ただ、そもそも必要なテストが存在しているかどうかは、どの数字も見ていなかった。存在しているコードがどれだけテストされているかは教えてくれるけど、テストすべきものが存在しているかまでは教えてくれないんですよね。
当たり前といえば当たり前です。でも実際に運用していると、100%という数字を見ると安心するんですよ。今回、普通にそこにハマりました。
これは全体を見た方がいいなと思って調べたら、SQLの半分近くにテストがありませんでした。しかも数ヶ月、誰も気づいていない。なかなかです。
そこだけ、ハーネスの外に出してた
じゃあ、なんでRepositoryだけこんなに抜けたのか。追っていったら、途中で入れた例外に戻ってきました。
GitHub Copilotのクレジット消費がかなり増えて、予算上これ以上は追加しないことになった時期があったんです。そこで、テストコード生成だけMS365のCopilotに逃がしました。MS365側からプロジェクトのソースコードを直接見られるわけではないので、テスト対象のクラスと依存関係をまとめて渡せる仕組みも作って、これは思ったよりうまくいきました。
ところが、Repositoryのテストだけはどうもうまく作れない。なので「RepositoryだけはGitHub Copilotで作るか、手書きでお願いします」としました。そこだけ、ハーネスの外に出したんですよね。
普段はInstructionsにルールを書いて、AIに実装させて、テストを書かせて、静的解析をかけて、最後に人間がチェックリストで確認する。チェックリストも、正直うんざりするくらいあります。でも、経験もスキルもかなり違う人たちに一定の品質を出してもらおうと思ったら、結局そうするしかないんですよね。知っている人なら普通に確認することでも、知らない人はそもそも確認しようと思わない。だから知識をできるだけドキュメントやInstructionsやチェックリストに落として、仕組みとしてやらせる。それがハーネスです。
今回も、ハーネスの中にいた部分はちゃんとできていました。抜けたのは、外に出したところだけです。
人に戻した瞬間、素の実力に戻る
Instructionsに書いてAIにやらせている間は、本人が毎回テストの重要性を意識していなくてもテストは作られるし、チェックリストに確認項目があれば少なくともそこは見ます。経験が浅くても、Javaをほとんど知らなくても、そのプロジェクトの中ではある程度同じところまで行ける。実際、僕はそうなるようにハーネスを作ってきました。
自分で言うのもあれですが、かなり強力に機能していると思います。もともと一人月くらいの仕事ができる人ならAIを使ってもっと速くなるし、まだそこまでのスキルがない人でも、ハーネスの中なら一人月に近いアウトプットが出せる。僕の中では、チーム全体にかけているバフみたいなものなんですよね。魔法陣の中にいる限り、全員の能力値が上がる。
で、今回はRepositoryだけその魔法陣の外に出した。その瞬間、本人の理解とか記憶とか注意力とか、そういう素の実力に戻って、ばらついた。
ハーネスの中で成果を出せることと、本人のエンジニアとしてのスキルがそこまで上がっていることは、別です。ここは間違いないです。いくら細かいチェックリストを作っても、知識そのものを短期間で移植できるわけではない。「ここを確認してください」は書けます。でも「そもそもこの結果はおかしくないか?」とか「この種類のバグがこの工程で出るのは変じゃないか?」みたいなところは、経験や理解がないとなかなか出てこない。
今回だって、マッピングエラーそのものはただのバグです。僕が引っかかったのはバグの内容ではなく、「これ、この段階で出るのおかしくない?」というところでした。自分で単体テストの方針もハーネスも作っているので、本来どこまで担保されているはずなのかを知っていた。だから違和感があった。もし「まあ結合テストだし、バグくらい出るよね」で終わっていたら、半分近くテストがないことには、まだしばらく気づかなかったと思います。
結局、クレジット買った方が安かった
あと、地味に効いたのがコストです。
GitHub Copilotのクレジット消費を抑えるために別のAIへ逃がして、その中でRepositoryだけ人手に戻して、結果としてそこにばらつきが出て、今から抜けた分のテストを用意することになった。普通に考えたら、追加でクレジットを買った方がはるかに安かったです。
まあ、クレジットを増やさないと決めたのは僕じゃないんですけどね。ただ、その制約の中でRepositoryだけ例外にしたのは僕なので、そこは分けて考えないといけない。今振り返れば、ハーネスの外に出した時点で「ここだけ別の方法でテストの存在を確認する」仕組みを一個入れておけばよかったんですよね。そこまでは考えられてなかった。
とはいえ、じゃあどこまで仕組みを作り込むのかという問題もあります。システム開発のプロジェクトって、同じ工程を何十年も回す工場とは違います。せいぜい一年くらいでチームが解散することも普通にあるし、次のプロジェクトでは技術も設計もメンバーも変わる。作ったハーネスがそのまま次へ引き継がれて、何年も改善され続けるとは限らない。だから、すべての例外まで完璧に仕組みに閉じ込めようとすると、今度は仕組みを作るコストの方が大きくなります。たぶん、この辺が現実的な限界なんだと思います。
AI駆動開発の話って、AIにどんなコードを書かせるかとか、どのモデルが一番賢いとか、そっちに寄りがちです。でも何ヶ月かチームで回してみると、だんだん気になるところが変わってきて、どこまでを仕組みで担保してどこから人に戻すのか、そしてハーネスの外に出たときにその人はエンジニアとして判断できるのか、という話になってくる。今回、Repositoryのテストが大量に抜けたことで、その境目がものすごく分かりやすく見えました。
結合テストのかなり早い段階で見つかったのは、本当によかったです。もっと後まで残ってたら、笑えなかったですね。
