t-wadaのブログ

【翻訳】テスト駆動開発の定義

このブログエントリでは、テスト駆動開発(TDD: Test-Driven Development)の考案者Kent BeckがTDDの定義を改めて明確化した文章を、許可を得たうえで翻訳し、訳者の考察を沿えています。

きっかけ

2023年の年末、テスト駆動開発(TDD: Test-Driven Development)の考案者Kent Beckは、substackにTDDに関するポストを連投して論戦を繰り広げていました。TDDはその誕生から20年以上が経ち、その間に「意味の希薄化」が発生して議論が噛み合わなくなっていました。意味の希薄化(Semantic Diffusion)とは、新しく作り出された用語が広まる際に本来の意味や定義が弱まって伝わる現象です。

私(和田)はTDDと関わりの深いキャリアを歩んできました。Kent Beckの著書『テスト駆動開発』翻訳者であることもあり、TDDの正確な理解を広めることに少しは責任のある立場だとも思います。一連のKent Beckのポストの中でも特に Canon TDD が面白かったので、Bluesky上で本人から許可を得たうえで翻訳を行い、ここに公開します。

【翻訳】テスト駆動開発の定義

KENT BECK

2023/12/11

これから書くことは、あなたがTDDを行うべきかどうかではない。責任を担うつもりならば、いかなる手法を選んだとしても、自分の仕事の質に責任を持つべきだ。

この文章は「TDDなんてクソだ!なぜなら <TDDではないものがここに入る> だからだ」といった意見に対する私からの回答だ。ひとつ典型的な例を挙げるなら「TDDなんてクソだ!なぜなら コードを書く前に全てのテストを書く のは大嫌いだからだ」だろう。何かを批評するつもりなら、実在する的をよく狙ってほしい。

  1. 網羅したいテストシナリオのリスト(テストリスト)を書く
  2. テストリストの中から「ひとつだけ」選び出し、実際に、具体的で、実行可能なテストコードに翻訳し、テストが失敗することを確認する
  3. プロダクトコードを変更し、いま書いたテスト(と、それまでに書いたすべてのテスト)を成功させる(その過程で気づいたことはテストリストに追加する)
  4. 必要に応じてリファクタリングを行い、実装の設計を改善する
  5. テストリストが空になるまでステップ2に戻って繰り返す

はじめに

Vic Wu がこのポストを図解してくれた:

TDDのワークフロー

テスト駆動開発(TDD: Test-Driven Development)について連日議論してきた中でひどく驚いたことがある。TDDの定義について世の中の合意が得られないのだ。私は書籍を執筆した際にできる限り明確に表現したつもりだったし、実際に明確だと思っていた。だが、そうではなかったようだ。残念でならない。

いまあなたが後述のワークフローとは違うことをしていて、それでうまくいっているのなら、おめでとうと言いたい。定義通りのTDDではないが、そんなことは誰も気にしない。手順に正確に従えば褒めてもらえるわけでもない。

いまあなたがTDDを批評しようとしていて、かつ、後述のワークフローを批評対象としていないのであれば、あなたは藁人形論法を使っていることになる。私が人生の貴重な残り時間を費やしてこの文章を書いている目的は、藁人形論法の駆逐だ。あなたにプログラミング方法を教えたいのではないし、褒めてもらいたいのでもない。

私は常日頃から楽観的かつ建設的であろうと努めてきたが、やむを得ず、この文章は率直かつ悲観的なものになるだろう。「人々は誤解している。本当はこうだ」といった具合だ。私は誰かのワークフローを批評するつもりはない。ただ、TDDの定義に対する世の中の理解を鋭く研ぎ澄ませたいだけだ。

概要

テスト駆動開発(TDD: Test-Driven Development)はプログラミングのワークフローだ。あるプログラマが、あるシステム(まだ無いかもしれないが)の振る舞いを変更する必要があるとする。TDDの狙いは、そのプログラマを支援して、システムを下記のような新たな状態に導くことだ。

  • それまで動作していたものは引き続き全て動作する
  • 新しい振る舞いは期待通りに動作する
  • システムはさらなる変更の準備ができている
  • プログラマとその同僚は、上記の点に自信を持っている

インターフェイスと実装の分離

多くの人が気づいていない最初の誤ちは、設計をひとまとめにして扱ってしまっていることだ。設計には2種類ある。

  • ある振る舞いはどのように呼び出されるべきかの設計
  • システムはその振る舞いをどう実装するべきかの設計

(学生だった頃、この2つは論理設計と物理設計と呼ばれ、それらを決して混ぜてはならないと言われたものだった。しかし、誰もその具体的な方法を教えてはくれなかったので、後に自力で方法を見つけ出さなければならなかった)

TDDのワークフロー

人間は出来損ないのコンピュータのようなものだ。これから説明する手順はコンピュータ上で動くプログラムのように見えるが、無論そうではない。このような書きかたをしているのは、プログラミングに慣れ親しんだ人たちに伝わりやすいようにしようという試みだ。私が「試み」と言ったのは、前述の通り、人々は「TDDなんてクソだ!私は <全く別のこと> をやって失敗した」と言ってしまいがちだからだ。

ステップ1. テストリスト

TDDの最初のステップは、あるシステムに振る舞いの変更が望まれているとき、その新しい振る舞いにおいて期待される動作をリストアップすることだ。「正常系はこうで、もしもサービスがタイムアウトしたらこうして、データベースにキーがまだないときはこうする。あとは……」

これは分析(analysis)だと言えるだろう。なかでも振る舞いの分析にあたる。変更後の振る舞いが満たすべき様々な動作を網羅的に考えよう。振る舞いの変更が既存の動作を壊さないようにする方法を考えているなら、それもテストリストに追加する。

よくある過ち: 実装の設計判断を混ぜ込んでしまうこと。まあ落ち着こう。内部実装がどうあるべきか吟味する時間は後からいくらでも確保できる。目の前のことだけに集中すれば、テストのリストアップがもっと上手く進むようになる(紙ナプキンに実装のスケッチをする必要があるならば、やってみてほしい。でも、それはたぶん必要ない。まあ試してみるのは止めないが)。

多くの人が、書籍『テスト駆動開発』の中に出てくるこのステップ1を見逃しているようだ。「TDDはいきなりコードを書き始める。いつ終わるのかも全然見通せない」という意見は、全くの見当外れだ。

ステップ2. ひとつテストを書く

テストをひとつ。準備と実行と検証(アサーション)が備わった、本当の本当に自動化されたテストを「ひとつだけ」書く(プロからのアドバイス: ときには、テストコードをアサーションから書き始めて、上に向かって逆向きに書き進めてみよう)。テストコードを書いている途中に設計判断が始まるが、それは主にインターフェイスの設計判断だ。そこに実装の設計判断も漏れ出すことがあるが、そちらは慣れてくるにつれて漏れ出しにくくできるようになる。

よくある過ち: コードカバレッジを上げるためだけに、アサーションのないテストコードを書いてしまうこと。

よくある過ち: 先にテストリストのすべての項目を具体的なテストコードに翻訳してから、ひとつずつテストを成功させようとしてしまうこと。もしも最初のテストを成功させる過程で設計をやり直したくなったら、投機的に書かれていたテストたちはどうなるだろうか……全部書き直しだ。この誤ったやりかたでは、テストリストの6項目めまでテストコードに翻訳した時点では、テストを実行してもまだひとつも成功しない。そんなときにどう思うだろうか……憂鬱になるか、退屈になるかだろう。

テストリストから次に書くテスト項目をひとつ選び出すのは重要なスキルであり、それは経験によってのみ得られるものだ。テストを選ぶ順番は、プログラミングの快適さと最終的な成果の両方に重大な影響を及ぼす(未解決問題: コードは初期条件に左右されるか)。

ステップ3. テストを成功させる

失敗するテストをひとつ書けたら、今度はシステムを変更してテストを成功させる。

よくある過ち: アサーションを削除して、テストが成功したふりをしてしまうこと。本当に成功させないとダメだ。

よくある過ち: テスト対象を実際に動かしたときの値をコピーして、テストコードの中の期待値にペーストしてしまうこと。これではダブルチェックにならず、TDDの妥当性確認(validation)としての価値が台無しになってしまう。

よくある過ち: テストを成功させる過程にリファクタリングを混ぜ込んでしまうこと。またもや「帽子を2つかぶってしまう」問題が出てきた。まずは動かし、それから正しくする。このやりかたが結局は脳にも優しい。

レッド(テスト失敗)からグリーン(テスト成功)にする過程で新しいテストの必要性に気づいたら、それをテストリストに追加する。その気づきがこれまでの作業を無駄にしてしまう場合は(例えば「ああダメだ、これでは空のフォルダを扱えない」)、それでもなおそのまま進めるか、それとも初めからやり直すかを判断しなければならない(プロからのアドバイス: 初めからやり直すが、今度は違う順番でテストを選んでみよう)。テストが成功したら「済」マークをつけてテストリストから消し込む。

ステップ4. 必要に応じてリファクタリングを行う

ここまで来たら、ようやく実装の設計判断を行えるようになる。

よくある過ち: この段階で必要以上にリファクタリングしてしまうこと。コードの整理整頓は気持ちがいいもので、ときには次のテストに向かうのが怖いとさえ感じてしまうことがある。どうすれば成功させられるか分からないテストの場合はなおさらだ(いま私のサイドプロジェクトはこれで行き詰まっている)。

よくある過ち: 早すぎる抽象化。重複はヒントであって、指令ではない。

ステップ5. テストリストが空になるまでステップ2に戻って繰り返す

コードの動作に対する不安が退屈に変わるまで、テストとコーディングを続ける。

(翻訳おわり)

翻訳してみて

訳してみて改めて思うのは、テスト駆動開発が広まる過程で本当に「意味の希薄化」が起こったのだなという実感です。「テスト駆動開発」という名前は世界中に届いたものの、その中身を知っている人は想像よりはずっと少ないのかもしれません。

私は新訳版『テスト駆動開発』の翻訳を行っているため、今回の内容に違和感はありません。書籍と今回のエントリの微細な違いといえば、重複の除去が「リファクタリングのヒント」扱いになったことくらいでしょう。TDDとはここで述べられているようなプログラミングのワークフローであるという定義に異論はありません。ワークフローであるということは、つまりテスト駆動開発とは開始条件と終了条件があり、条件分岐と状態遷移の組み合わせによって構成されたプログラミング手順であるということです。

日本でのTDDの理解と誤解

振り返って日本における現状を考えてみると、やはり日本でも「意味の希薄化」は起こっています。技術が広まるとはそういうことなのでしょう。「テスト駆動開発」や「TDD」という名前で若干違うことを指しており、どこか噛み合っていない議論は日本でもたびたび目にします。今回このブログエントリを読まれて「TDDは自分が考えていたものとは結構違うな」と思われた方もいらっしゃるのではないでしょうか。

日本でのテスト駆動開発の誤認にはいくつかの種類があると考えています。1つめは、今回のようなワークフローではなく、テスト駆動開発を構成する技術プラクティスをテスト駆動開発と呼んでしまうことです。典型的には下記のものを「テスト駆動開発」と呼んでいる場面があるように思います。

  • (Jestやpytestのようなテスティングフレームワークを使って)テストコードを書くこと
  • 開発者が自分でテストコードを書くこと
  • テストコードを実装よりも前に書くこと

これらはテスト駆動開発の必要条件ではありますが、十分条件ではありません。今回のKent Beckの意見を尊重するならば、これらを「テスト駆動開発」と呼ぶのは誤りであるということになります。

それらは何と呼ぶのか

では結局それらは何と呼ぶのが良いでしょうか。様々な文献や過去の議論を調べると、以下の対案が挙がります。

  • テスティングフレームワークを使ってテストコードを書くこと(あるいは、書かれたテストコードそのもの)は「自動テスト(Automated Test)」と呼ばれます。誰が書いても構いませんし、いつ書いても構いません
  • 開発者が自分でテストコードを書くこと、開発者自身が自動テストを書きながら開発することは「開発者テスト(Developer Testing)」と呼ばれます。テストコードを書くタイミングは後からでも構いませんが、実装を行うタイミングに近ければ近いほど効果が高まります
  • テストコードを実装よりも前に書くことは「テストファーストプログラミング(Test-First Programming)」あるいは短く「テストファースト」と呼ばれます。テストコードの書き手は開発者自身であることがほとんどです(が、開発する本人以外の人が先にテストコードを書くこともあります。他の人が書く場合はテスト駆動開発の構成要素にはなりません)

「テスト駆動開発」という名前を使おうとするときには、これらの名前もあわせて思い出して頂ければ幸いです。

そしてKent Beckも念を押していますが、テスト駆動開発を行うこと自体で価値や正当性が生まれるわけではありません。手段の目的化には特に注意しなければなりません。世の中で(ここで説明した誤認の影響もあり)「テスト駆動開発の効果」と言われているものの多くは、実際には自動テストや開発者テストの効果です。

自動テストを書きましょう。開発者テストを行いましょう。この2つは本当におすすめです。それらに比べれば、テストファーストやテスト駆動開発は「お好み」です。個人の好みや状況に応じて行うかどうかを決めれば、それで十分です。

テストコードを先にたくさん書くことではない

TDDの構成要素の中では、特にテストファーストの「先にテストコードから書く」という印象が強いようです。また、テスト駆動開発は品質保証の手法であるという誤解も特に日本では根強いように思います。これらのイメージが独り歩きすると、テストコードを先に書きすぎるといった「よくある過ち」につながります。

設計せずにいきなりテストコードを書き始めることではない

テスト駆動開発の誤認の種類は、まだまだあります。例えば「TDDとはレッド、グリーン、リファクタの3ステップを繰り返すものである」です。ここで抜け落ちているのはテストリストですね。良くも悪くも、Red-Green-Refactorのイメージが強すぎました。

Kent Beckの今回のブログエントリや書籍『テスト駆動開発』を読むと分かるのは、TDDのステップは、より正確に表現するなら「リスト、レッド、グリーン、リファクタ」であるということです。これは今回の翻訳記事で初めて認識された方も多いのではないでしょうか。TDDには設計のステップが明示的に存在しています。

テスト駆動開発はテストコードを先にたくさん書くことではありませんし、設計せずいきなりテストコードを書き始めることでもありません。テスト駆動開発は設計のやりすぎも設計のやらなさすぎも起こりにくくなるように設計されたワークフローになっています。

「自動テストとテスト駆動開発、その全体像」あわせて公開

ここまで述べてきたような認識の齟齬をそのままにしておくとコミュニケーションにも影響が出てくるので、まとまった量の情報が必要だとは考えていました。そこにタイミング良く執筆依頼をいただき、雑誌『Software Design 2022年3月号』にて、第2特集「そろそろはじめるテスト駆動開発」の第1章「自動テストとテスト駆動開発、その全体像」を執筆する機会に恵まれました。その際に私が書いた章のポイントは下記のものでした。

  • 自動テスト関係のプラクティスを 1.自動テスト 2.テストファースト 3.テスト駆動開発 の3段階に分ける
  • 3つそれぞれの効果や注意点を包括的にまとめる
  • テスト駆動開発まで行かずとも、自動テストを行えば十分価値があると伝える

このときの雑誌記事はかなり力を入れて書いた文章であり、自分でも納得のいくものが書けたのですが、残念ながら雑誌は1、2ヶ月で書店から姿を消してしまうものです。

今回Kent Beckの「TDDの定義」を訳す過程で、件の雑誌記事も長く残るところに公開したいと思うようになりました。そこで技術評論社 Software Design 編集部の皆様に背景や動機を説明したところ、快く承諾くださり、gihyo.jpにて公開いただけることになりました。誠に感謝いたします。今回の翻訳記事とあわせて、こちらの記事もぜひご覧いただければと思います。

gihyo.jp

おわりに

テスト駆動開発は誕生から20年ほど経ち、その間に良いところも悪いところもほぼ出尽くしています。テスト駆動開発とその構成要素の正確な姿を伝えることは、優良誤認や手段の目的化を防ぐことにもつながると考えています。今後も折に触れてテスト駆動開発やソフトウェアエンジニアリングのトピックについて情報発信していきたいと思います。