TDDは「開発者テストのTips集」t-wada氏が改めてひも解く“本質”

2024年7月16日

プログラマ、テスト駆動開発者

和田卓人

学生時代にソフトウェア工学を学び、オブジェクト指向分析/設計に傾倒。執筆活動や講演、ハンズオンイベントなどを通じてテスト駆動開発を広めようと努力している。『プログラマが知るべき97のこと』(オライリージャパン、2010)監修。『SQLアンチパターン』(オライリージャパン、2013)監訳。『テスト駆動開発』(オーム社、2017)翻訳。『事業をエンジニアリングする技術者たち』(ラムダノート、2022)編者。テストライブラリ power-assert-js 作者。

日本におけるテスト駆動開発(以下、TDD)のエバンジェリストとして知られる和田卓人さん。TDDが世に出て20年あまりが経ち、開発者の間でその名が広がっています。その一方で、和田さんは「TDDの本来の意味を知らなかったり誤解したりしている人たちもかなり増えている」といいます。

今回は、TDDは本質的にどんな課題を解決するために生まれたのかを、改めて和田さんの言葉で整理していただきました。

まず本題に入る前に、今回のお話の基本となる、TDDの実施方法をおさらいします。
既にご存知の方はこちらから本編にお進みください。

TDDの考案者であるKent Beck氏の定義によると、TDDは次の5つのステップを繰り返すことによって、ユーザーが求めるソフトウェアプロダクトを生み出すための開発技法です。

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

各段階をもう少し詳しく見ていきましょう。

ステップ1. テストリストを書く
最初のステップは、システムの機能の変更に際して、新しい機能に期待される動作をリストアップすることからはじまります。新しい機能が満たすべき動作を網羅的に考えることが重要です。この時点でインターフェースの設計は行いますが、実装の設計判断は行いません。

ステップ2. ひとつテストを書く
先に挙げたテストリストに基づき、準備と実行と検証(アサーション)が備わった自動テストを「ひとつだけ」書きます。一度に複数のテストコードを書いたり、コードカバレッジを上げるためだけに、アサーションのないテストコードを書いたりしてはいけません。

ステップ3. テストを成功させる
テストが通らなかったら、今度はテストが成功するように、システム側のコードを変更します。テストが成功したら「済」マークをつけてテストリストから消します。ステップ2、3の過程で新しいテストの必要性に気づいたら、それをテストリストに追加します。

ステップ4. 必要に応じてリファクタリングを行う
ここまできたら実装の設計判断を行います。この段階では必要以上にリファクタリングせず、ある程度目処がついたら次のテストに向かいます。

t-wadaのブログ「【翻訳】テスト駆動開発の定義」を参考に編集)

「動かないかもしれない」という不安から解放してくれた唯一の手法だった

——今日はよろしくお願いします。まず、和田さんとTDDの出会いを教えてください。

和田:TDDと出会ったのは、いまから20数年前のことで、私にアジャイルソフトウェア開発を指南してくださった、まさーるさん(石井勝氏)がきっかけです。まさーるさんがその当時、エクストリームプログラミング(XP)を編み出したKent Beckが新しい本を書いていて、レビュアーを募集していると教えてくれたんです。「興味があったら読んでみたら」と勧めてくれたのが、後に原書が発売された『Test-Driven Development: By Example』でした。

和田:太っ腹なことにKent Beckは、TDDメーリングリストを通じて、執筆中の生原稿を無償公開しており、サンプルコードを「写経」しながら読んだ記憶があります。それがTDDとの出会いです。

——生原稿を読んで「これこそが自分の求めていた開発メソッドだ」と確信されたわけですよね。なぜそう感じたのでしょうか?

和田:TDDは、自分が当時感じていたプログラミングのつらいところを解決してくれたからです。

当時の私にとってつらかったのは、期待通りに動くかわからないままコードを書き続けることでした。巨大なプロジェクトの一部としてコードを書いていると、自分の書いたコードが本当に想像通りに動くかどうかがわかるのは数カ月先で、もう何をどう書いたのかも忘れた頃です。コードを書くのは好きなのに、自分が書いたコードが適切なのか分からない不安と常に隣り合わせだったんです。

でもTDDをやってみると、書いたコードが自分の考えた通りに動いているか、手元ですぐに答え合わせができます。テストが通るたびに「よし、ちゃんと動いているぞ」と確認できるから、「動かないかもしれない」という不安を感じなくて済む。結果がすぐにわかるから「もうちょっといい書き方ができそう」とか「もっとこうしたらいいんじゃないか」と思った時点で直せます。テストが成功するたびに、開発者としての自信や自己効力感が高まっていくように感じました。

それに、書いたコードがちゃんと動いているかを「自分だけ」で確認できるのも、私にとってはありがたかった。うまく書けていない自分のコードを誰かに見せて、指摘されてから直すのではなく、自分だけでブラッシュアップできる。これは当時の私にとって、とても価値のあることでした。

TDDのメリットの多くは「自動テスト」のメリット

——改めてTDDの利点を教えていただけますか?

和田:TDD自体がシステムにもたらす利点は「“つくりやすい”ではなく、“使いやすい”設計ができること」です。

テストのない状態でコードをつらつらと書いていくとどうしても、自分にとってつくりやすい、書きやすい設計に寄っていってしまいます。しかし「テスト」は、使う側が期待している動作をインターフェースから定義し、それが実現できているか確認するものです。TDDのステップを守って、テストを先に書き、それに対応するコードを書いていくと、使う側の期待に沿ってコードを書くことになります。「ユーザーにとって使いやすい設計」が行いやすくなるのです。

ただ、TDDのメリットだと言われがちな「保守性を高められること」は、正確にはTDDに取り組む中で「自動テストを書くこと」によって得られる効果です

——より詳しく教えてください。

和田:テストがないコードは、どんな条件で正常に動いているのか、また、どんな条件で壊れるのかわかりません。こうした「たまたま動いているだけ」の爆弾のようなコードを抱えていると、日々の改善や新規開発が全て「爆弾処理」のような仕事になってしまいます。

ここで言う「爆弾」が、いわゆる「技術的負債」です。最初は小さな爆弾でも、それが爆ぜるのを恐れ、改善を躊躇していれば、どんどん大きくなっていきます。

爆発したらどうしよう、爆発した後に直せなかったら、爆発に気づくことすらできなかったらどうしようと、常に緊張しながらコードに手を入れるのは、恐ろしいしつらい。だからできるだけやりたくない。そうして爆弾を触らないままにしておくと、爆弾のサイズがどんどん大きくなって数も増えていきます。さらにそのせいで、今まで「動くコード」だったものが、バージョン変更など外的要因の変化によって「動かないコード」になったりして、手をつけられなくなってしまいます。

でも、あらゆる要所に自動テストが用意されていれば、変更を加えるたびに、テストが通るか通らないかで、爆発が起きたか、つまり既存のコードを壊したかを一目で判断できます。すると、詳細をよく把握していない、覚えていない箇所でも、コードに手を入れて改善するのが怖くなくなっていくのです。

「テストが書けなくなる呪い」を解くために、開発者テストやTDDが生まれた

——自動テストを書けば「コードを躊躇せず改善できる」という大きなメリットを得られるのに、なぜテストを書かないまま開発が進んでしまうのでしょうか。

和田:新規プロダクト開発の場合、テストを書かずに機能開発だけに重きを置いて走り出すのは、ある程度合理的ではあります。でも「後から自動テストを書けばいい」と思っていても、実際やろうとするとかなりつらいんですよね

なぜかというと、それまでに積み上げた「テストがないコード」は、テストを書くことを考慮した設計ではないからです。既存のコードはそのままでテストだけ書こうとしても、テストが書きにくいから、既存のコードに何かしら手を入れなくては、そもそもテストを書けない。

しかし、自分の記憶にあるコードでなければ、どんな意図で今の形になっているのかもわかりません。それに既存のコードにはテストが書いてありませんから、手を入れた結果どこかが壊れるかもしれないし、壊れたことに気づかないかもしれません。

既存のコードを安全にいじるためにテストを書きたかったのに、テストを書くためにはまずコードをいじらざるを得ない。これが強力な呪いになり、テストを書く一歩を踏み出せなくなってしまいます。この呪いのせいで、テストを書き始めるタイミングを失ったまま開発が進んでしまうのです。

「テストを書くタイミングと、実装のタイミングが離れていること」「テストを書く人と実装する人が分かれていること」。開発で「後からテストが書きづらい」という状態に陥ってしまう理由は往々にしてこの2つです。

であれば、実装のタイミングで自動テストを一緒に書けばいい。かつ、実装する人がテストも書けばいいはずです。これを説いているのが「開発者テスト(Developer Testing)」という、テスト容易性を担保するための開発手法で、そのやり方の一例がTDDなのです

——開発者テストが、どのようにTDDにつながっていったのでしょうか。

和田:たとえば、開発者テストにある「実装と近いタイミングで自動テストを書く」に対して、TDDでは「近いタイミングというなら、実装の直前にテストを書いてもいいんじゃないか」と順序をひっくり返しています。

また、「テストを先に書く」が行き過ぎて、実装の前にテストを思いつく限りたくさん書いてしまうと、テストを書くのと実装のタイミングが離れてしまいます。それを防ぐために、TDDでは「先にテストを書くのは1件だけ」と制約を設けています。

——となると、TDDは「開発者テストをうまく進めるためのTips集」のようなイメージでしょうか?

和田:私はそう思っています。TDDは、Kent Beckが「開発者テスト」をより漏れなくスムーズに進めやすくするために考案した方法論ではないか、と。

この関係性を理解している人は、TDDを緻密に実践しなくても、これらの効果を得ることができます。こうした人々で構成された開発チームは、後からでもテストを書くし、後からテストを書けるような、テスト容易性の高い設計でコードを書いています。

でも、テスト容易性の高い設計を最初からしておくのは、一般のプログラマーには難易度が高すぎる。それを簡単に、あらゆる人ができるようにするために、TDDという方法論があるのだと私は考えています。

▲取材はオンラインで行いました

TDDにしかない強みは「プログラミングのやり方」を学べること

——世の中には他にもたくさんの開発手法がありますが、TDDでしか実現できないことはなんですか?

和田:TDDでしか実現できないことは、正直ほぼないでしょうね。現に私自身も、TDDを他の手法と組み合わせながら使っていますし。

ただ、TDDの「強み」と言えるポイントは2つあります

1つは、若手が仕事の進め方を学ぶサポートになることです。社会人1~2年目の若手メンバーから悩みを聞いたときに一番に挙がるのが、実は「タスク管理」です。彼らは、日々のタスクをどう分解してどういう順番で解決していくべきなのかを、うまく掴むことができず悩んでいます。実装のノウハウを具体的にまとめた技術書や研修プログラムが充実してきた今でも、プログラミング自体の手順や考え方は、教わるものでなく自分で身につけるものとされていますから、悩むのも当然です。

TDDはプログラミングの手順を明確にわかりやすく定義している数少ない手法で、かつ自己効力感が続くように、小さなステップをくるくる回すようになっています。そのため、TDDに則った開発を経験する中で、「問題を分解して一つひとつ倒していく」という一種の仕事術を身につけることができます。これはプログラミングのタスク消化はもちろん、それ以外の仕事を進めるときにも役立つはずです。

もう1つの強みは、短期の効果と中長期の効果がバランスよく得られることです。TDD以外の開発手法は一般的に、効果が感じられるようになるまで少しラグがあります。対してTDDは、最初のテストが成功した時点で、先述の自己効力感が得られるようになっています。そのうえ中長期、およそ数週間~1カ月以内を目安に、「いじれないコードが減る、生まれない」という効果を感じられるようになっていきます。今すぐ感じられる短期の効果が、中長期の効果を得られるときまでのアクセルになりますから、このバランスの良さは、TDDの大きな強みだと考えています。

取材:武田敏則(グレタケ)
構成・編集:光松瞳、王雨舟

関連記事

人気記事

  • コピーしました

RSS
RSS