1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI 時代に Kent Beck の TDD をきちんと学び直す — 「小さく回す」ことに残る意味

1
Posted at

はじめに

AI にお願いすると、テストコードもプロダクションコードも一気に書き上げてくれる時代になりました。
それでも Kent Beck 自身が「古典的な TDD は AI 時代にこそ効く」と言い続けているのは、なぜでしょうか。
Kent Beck は AI エージェントを「願いを叶える精霊 (Genie)」に喩えます。
精霊は願いを叶えるけれど、何を願うかを正確に指定しないと予期せぬ副作用がある。
TDD はその願いを先に具体化する道具だ、というのが Kent Beck の主張です。

TL;DR — この記事の結論

Kent Beck の TDD の本命は「テストを先に書くこと」ではなく「小さく回すこと」そのものだ、と学びました。
合格条件を先に固定する道具として AI に効くだけでなく、小さく回すからこそ得られる副産物があります。
それは AI に一気に書かせても手に入らないもので、この記事では拍・ギア・ダウンシフト判断・リストという4つの装備と、副産物3つを追いかけていきます。

TDD は「3拍子のリズム」である

「小さく回す」とは具体的に何をすることなのか。まずは4つの装備で分解して見ていきます。ひとつ目が、この章の話。3拍子のリズムです。

TDD は「Red」「Green」「Refactor」という3拍子で回る規律です。
3語自体は聞いたことがあっても、それぞれの拍で頭の使い方が違うことは、あまり明示的に語られない気がします。
手が止まる本当の理由は、この「拍の切り替え」ができていないところにあることが多いと分かりました。

image.png

目的 判定基準
Red 問いに絞る 1つのテストが1つの問いになっているか
Green 速度で通す 汚くていい、通ることだけを考える
Refactor 次の拍への足場を作る 重複を消す、明日の自分に譲る

拍ごとの美学は AI に依頼するときのプロンプトの粒度でもあります。
「まず通す実装だけ書いて」と「重複を消して」は、別のプロンプトで分けたほうが、AI もぶれずに動いてくれます。

Red バーの美学: 問いに絞る

Red バーの拍では、テストの「問い」だけに集中します。
実装のことは考えません。
「何を確認したいか」を assertEquals(期待値, 呼び出し) の1行で先に書いてしまう「Assert-First」と呼ばれる技法があります。
期待値・呼び出し・存在すべきクラス名を、この1行が同時に問いかけてくれます。

意識したいのは、速度がタイピングの速さを指すのではないところ。
Red の拍における速度とは、次の Green バーへ到達するまでの時間、つまり秒単位のフィードバックのことです。
だから Red では焦らず、1問ずつ問いに絞ります。

Green バーの美学: 速度で通す

Green バーの拍では美学が180度反転します。汚くていい。定数を返してもいい。if の連打で通してもいい。とにかく緑バーを取り戻すことだけ考えます。

言い換えれば、問題を1つに絞るということです。
同時に「動く」と「綺麗」の両方を追いかけると、たいていどちらも中途半端になります。
まず「動く」だけを解決してしまい、「綺麗」は次の Refactor 拍に譲る、という分担です。

Refactor の美学: 次の拍への足場を作る

Refactor の拍では、重複を敵とみなします。テストコードと実装コードの間の重複、実装コード内部の重複、いずれも見つけたら黙って消していきます。

Refactor を省略してしまうと、「テストがあるだけの汚いコード」がどんどん溜まっていきます。
Green バーで許した罪は、この拍で必ず償う、というのが Kent Beck が繰り返し釘を刺すところです。

Refactor は今の Green バーを整えるだけでなく、次の Red 拍へ渡すコードの状態を作る作業でもあります。
次に何を書き始めても、少なくとも足元は崩れない。その安心感の準備をしています。

リズムを保つ3つのギア

3拍子の話が入ったところで、次は Green バーへの行き方の話に進みます。
緑バーへの道は1本ではありません。
「速度」と「確実性」のトレードオフで3種類のギアがあり、状況に応じて切り替えます。

(前章のとおり、ここでの速度は「拍の刻みの速さ」を指します。タイピング速度の話ではありません。)

image.png

3ギアは AI への依頼粒度でもあります。

  • Obvious は、AI に一気に書かせるお願い
  • Fake It は、人間が定数返しの足場を作ってから AI に精緻化を頼むやり方
  • Triangulate は、複数のテスト例を AI に投げて抽象化を強制するやり方

それぞれのギアと AI の使い方が、こう対応します。

Obvious Implementation: 3速のギア

実装が明らかなら、そのまま書きます。
TDD の日常運転はこのギア。
関数の中身が頭の中で1〜2秒でイメージできるなら、Fake It や Triangulate に落とさず、直接書いてしまいます。

条件は「自信があるとき」だけです。
自信がないままここで書き続けると、赤バーが連発して緑バーがどんどん遠のきます。
自信の見極めが、Obvious の実質の判断ポイントです。

Fake It: 2速のギア

Fake It では、まず定数を返して緑バーを確保します。そのあと「テストの値との重複」を消していく形で、少しずつ定数を変数に置き換えていきます。

たとえば add(2, 3) == 5 というテストがあるとして、初期実装は次のようになります。

# Before: 定数を返して緑バーを取り戻す
def add(a, b):
    return 5

# After: 「5 は 2 + 3 だ」に気づいて変数化
def add(a, b):
    return a + b

一度緑バーを取り戻してから、実装の抽象度だけを段階的に上げていく順序です。
見た目はズルい実装ですが、心理的な効果は大きいと分かりました。
動かない状態のまま考え続けるより、動く状態から考え続けるほうが、頭は圧倒的にクリアに動きます。

Triangulate: 1速のギア

Triangulate は、設計方向がまだ見えていないときの最終保険です。
1つのテストだけでは return 5 で凌げてしまうところに、2つ目・3つ目のテストを追加して、抽象化を強制します。

add(2, 3) == 5add(4, 5) == 9 の2本があれば、return 5 では通りません。
ここでようやく return a + b に到達します。
設計判断を、テストの数が引き上げてくれる、という発想です。

日常運転で使うギアではありませんが、どう抽象化していいか分からない場面での安全網として持っておくと安心です。

詰まったらダウンシフトする

ここまでで、拍とギアが揃いました。
この章は前章の応用編で、Fake It の手順そのものは扱いません。扱うのは「実装中の判断」の方です。
詰まりの検知は、AI に投げる前に、人間として何がわからないかを見極める境界線の作り方でもあります。

赤バーで「驚く」のは異常のサイン

Obvious のギアで書いたのに赤バーが出た瞬間、特に「え、なんで?」と思ったとき、それは自分が思っているほど問題を理解できていない、というサインです。

驚きは自然な反応です。大事なのは、驚きをダウンシフトのトリガーとして扱えるかどうか。ここで大股のまま押し切ろうとすると、赤バーが続き、リズムが崩れます。

なぜ緑バーが「考える足場」になるのか

驚いたら、いったん Fake It にダウンシフトして、緑バーを取り戻します。手順は前章のとおりで、ここで確認しておきたいのは心理的な意味の方です。

Fake It のダウンシフトが人間の頭に効くのは、次の3段が働くからだと分かってきました。

  • 緑バーは「壊してない」ことの保証になる
  • 以降の変更は Refactor として、期待どおりでなければ元に戻せる
  • その「戻せる状態」が、頭を落ち着かせて何が分かっていなかったのかを見つめられる状態に戻してくれる

Obvious で書いたコードを Fake に下げることに、心理的な抵抗があるかもしれません。
「後戻り」に感じるからです。
これは歩幅の問題であって、プライドが関わるところではないと気付きました。

それでも詰まるなら、席を立ちましょう。散歩でもコーヒーでもいい。頭を疲れさせたまま押し切ることが、TDD のリズムを一番壊します。

Test List を頭の外に置く

3拍子・3ギア・詰まりの判断まで揃いましたが、実装のリズムにはまだ1つ足りない道具があります。
実装中に湧く「そういえばこれもテストしたい」という発見を、どう扱うかです。

実装中に湧く欲求が拍を乱す

Green バーの拍で1つ通したとたん、頭の中に「そういえば負の値のときは?」「あ、ゼロ除算のケースは?」と別の問いが湧いてきます。
ここで反射的に負の値のテストを書き始めると、いま集中していた本題の拍が乱れてしまう。

かといって「あとで思い出そう」も機能しません。湧いた欲求は数分後には忘れます。そして、そのまま埋もれます。

記号で「拍を戻す」動線を作る

Kent Beck の Test List は、この問題への具体的な処方箋です。
実装中に思いついたテストを、その場でリストに書き足すだけで、次の拍に戻る。
書き足すのに5秒。
それだけで欲求が保管されるので、頭は本題に戻れます。

記号で状態を区別すると、リストが動線として機能します。

記号 状態
未着手
いま実装中
取り消し線 完了
作業中に発見して追加した項目

取り消し線を引きたい動機が燃料になる

面白いことに、この記号ひとつで進捗の見え方が変わります。
「☐」を「取り消し線」に変えたい、というささやかな欲求が、次の Red 拍を回す燃料になる。
ゲーム性が自然に効いてきます。

Test List にはもう1つ、AI 時代の実利もあります。
頭に湧いた「これもテストしたい」を書き出しておけば、次に AI に依頼するときの合格条件リストが、そのまま準備されている状態になる、というところです。

小さく回すと見えてくる3つのこと

ここで記事の最初の疑問に戻ります。AI が一気に実装できる時代に、なぜ小さく回すのでしょうか。

答えの半分は「合格条件を先に固定するため」です。
これは AI エージェントが「テストを通すためだけに実装をチートする」挙動を防ぐ、実利のあるガードレールになります。

半分だけではありません。
小さく回すからこそ得られる副産物があって、それは AI に「一気に完成品を書いて」とお願いしても絶対に手に入らないものだ、と分かってきました。

副産物 AI 時代における実利
設計が事後的に現れる AI 生成コードのリファクタリング判断で「現れるパターン」を認識できる
テストの臭いは設計の臭い AI が量産するコードの臭いを早期に検知できる
何をテストしないかがわかる AI 量産時代の「全部テストは不可能」問題への直接の答えになる

重複を消すと、設計が事後的に「現れる」

小さく回しながら「重複を消す」ことだけをリファクタリングの指令にする、というのが TDD の規律です。
すると不思議なことが起こります。
事前に設計を決めていないのに、Value Object や Composite といったデザインパターン的な構造が、事後的にコードに立ち現れてきます。

たとえば同じ計算パターンが2箇所に現れた瞬間、それを Extract Function(関数として切り出す)します。

# Before: 似た計算が並んでいる
total_a = price_a * qty_a + tax_a
total_b = price_b * qty_b + tax_b

# After: Extract Function で「現れる」抽象
def compute_total(price, qty, tax):
    return price * qty + tax

3箇所目が出てきたとき、今度は「引数の集合を1つのオブジェクトとして扱いたい」という欲求が湧きます。
そこで、そういうクラスを作る。
設計を先に決めたのではなく、重複除去の副作用として現れた、という順序です。

Kent Beck の言葉で言えば、TDD は分析・設計・スコープコントロールの技法です。
テスト技法ではなく設計の技法として機能する、という主張は、この「現れる」現象を指しています。

AI に「Value Object 使って設計して」といきなり頼むと、過剰設計のコードが返ってくることがあります。
小さく回して現れるのを待つ視点があると、その過剰設計に気づいて減らせるようになりました。

テストの臭いは、設計の臭いを教えてくれる

テストコードが書きにくい・読みにくいとき、それは実装コード側の設計に問題があるサインです。テストの書き方が下手なのだ、と自分を責める必要はありません。

代表的なのは「Long Setup」です。
1つのテストのために20行のセットアップコードが必要なら、そのオブジェクトはたぶん大きすぎます。
もっと小さいオブジェクトに分割するべきタイミング、というシグナルになります。

もう1つが「Fragile Tests」です。
関係ないはずのコードを変えたのに、なぜか別のテストが赤くなる。
これは、コード内で遠隔作用が起きているサインです。
見えないところで依存関係が絡み合っているので、分離するか、逆にまとめるかの判断が要ります。

Kent Beck が挙げる4大テスト臭のうち、残り2つは実装コード側の問題をこう示唆します。

臭い 実装コード側の意味
Setup Duplication(複数テストで似た準備が並ぶ) オブジェクト同士が密結合している
Long Running Tests(テストが遅い) 単体で単体をテストできない構造になっている

AI が量産するコードは、こうした臭いを抱えたまま構文的には正しい、ということがあります。
臭いを検知する視点があると、AI 生成物の品質を人間として担保できるようになります。

何をテストしないか、がわかる

小さく回していると、どこまでテストすべきかの判断力も自然に育ってきます。
Kent Beck の言葉で言えば、テストの対象は「自分が書いた条件分岐・ループ・演算・多態」だけです。

対象 テストする? 理由
自作の add、割引計算の分岐、独自の状態遷移 する 自分が書いた分岐・演算だから
標準ライブラリの sorted()dict しない 信頼して原則対象外
外部 SDK のリクエスト送信 しない 外部の責任範囲

この判断を単純化する目安として、Kent Beck は MTBF という考え方を挙げています。
MTBF は Mean Time Between Failures(平均故障間隔)の略で、そのソフトウェアをどれくらいの頻度で壊してよいかの指標です。

このアプリを「1年壊れない」目標にするなら、頻発する境界だけテストすれば足りる。
「10年壊れない」ペースメーカーのようなソフトウェアなら、極稀な条件も対象にすべき。
目標に応じて、テストする範囲が自然に決まってきます。

Kent Beck の言葉で「Test until fear becomes boredom(恐怖が退屈になるまでテストを書く)」というのがあります。
恐怖が退屈に変わったところで手を止めていい、という判断基準です。

AI が量産する時代には、全部テストするという発想が現実的でなくなりました。
何をテストしないかを自分で判断できる力は、そのまま生産性の差になるのだと分かりました。

おわりに: 明日から試す3つの動作

小さく回す装備の3拍子・3ギア・ダウンシフト判断・Test List と、小さく回すから得られる副産物3つを、追いかけてきました。
ここまでで、TDD の核心が「小さく回すこと」自体にあると腹落ちしました。

Kent Beck 自身が、AI エージェントと組み合わせた TDD を「superpower」と呼んでいます。
精霊が願いを叶える速度と、人間が願いを具体化する規律が組み合わさったときの効き方を指してのことです。
古典的な TDD は、AI 時代に古びるどころか、AI 時代の武器として磨きなおされる場所なのかなって思いました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?