テスト駆動開発(TDD)はもう終わっているのか? Part 2

本エントリは翻訳リクエストより投稿いただきました。
ありがとうございます!リクエストまだまだお待ちしております!

前編はこちらです

4:テストに伴うコスト

2014年5月27日

今回のテーマは、テストとTDDのマイナス面です。テストをやりすぎることがあるか、そして機能的なコードよりテストを重視するチームには問題があるかという点について議論しました。

議事録

Davidが会話の口火を切りました。「トレードオフについて話すなら、当然そのマイナス面について理解しなければならない。なぜなら、欠点のないトレードオフは存在しないからだ」 このあと彼は続けて、TDDは開発者に何かを強制するわけではないが、ある一定の方向に導くことは確かだと言いました。それから、最初の問題点として、テストの過剰な実施を取り上げました。TDDでよく言われるのは、テストに失敗せずして1行のコードも書くべきでないということです。Davidも当初はこの考え方を合理的だと思っていましたが、そのうち、テストをやり過ぎる傾向が出てきました。例えば、1行の本番用コードに対してテストコードが4行もあるようなケースです。こうなると、振る舞いを変えたい時には、より多くのコードを変更しなければなりません。Davidは、「テストを書くことで給料をもらっているわけじゃない。テストを書くのは自信をつけるためだ」というKentの言葉を取り上げて、Kentと私に、全ての本番用コードに対して逐一テストを書くのか尋ねました。

Kentは「場合による」と返答し、「恐らく、これからどんな興味深い質問をされても、私はそう答えるんじゃないか」と続けました。JUnitでテストファーストを忠実に行えば、十分に満足できる結果が得られるため、TDDを使うからといって、テストが常に過剰になるとは限らない、というのが彼の意見です。Herb Derbyは、それぞれのテストがカバーする固有の対象範囲を差分カバレッジという言葉で提起しており、差分カバレッジがゼロのテストは、何らかのコミュニケーション用途を提供でもしない限り、削除すべきだとしています。Kentによると、彼はsystem-yのテストを書き、それを実装するためのコードを書き、リファクタリングをし、最終的に当初のテストを放り出すことがよくあるそうです。テストを放り出すと聞くと、多くの人が取り乱すほどに驚きますが、それが何の利益も生み出さないのであれば、捨ててしまった方がいいでしょう。複数の方法で同じことがテストされる場合、それは結合しているということで、それだけコストがかさむわけですからね。

私は、コードが過剰にテストされることは確かにあると言いました。例を挙げろと言われれば、ThoughtWorksは非常にテストを重視するため、それに当てはまるでしょう。適度な実施というのはなかなかに難しく、多すぎることもあれば逆に少なすぎることもあります。極端に過剰でなければ、私自身は多すぎるのを好む方です。コードの全ての行をテストするという点については、「もし任意の1行を不適切にいじった場合、テストは失敗するか?」という問題を考え、折に触れて、ある行を意図的にコメントアウトしたり、条件節を逆転させたりして、テストが実際に失敗するかを試してみることがあります。もう1つのメンタルテスト(Kentから教えてもらいました)として、破損する可能性のあるものだけを見繕ってテストしたりもします。ライブラリは(それが相当に悪い状態でなければ)動作するのを前提として考えていますが、これについても、ライブラリの使い方を間違えた場合、その誤用でどのような重大な問題が起こるか、ということを試すことがあります。

Kentは、テストコードの行数と本番用コードの行数の比率は、あてにならない指標だと断言しました。そのような考えに至った理由として彼が挙げたのが、Christopher Glaeserがコンパイラを書いているのを見た経験です。Christopherはコンパイラの各行に対して、テストコードを4行ずつ書いていたそうです。なぜかというと、コンパイラの結合度が高いためで、シンプルなシステムであれば、もっと小さな比率でも済むでしょう。Davidは、コード内の1行のコメントアウトを検出するということは、テストカバレッジが100%だということを意味すると言いました。破損の可能性のあるものは追及に値するという意見については、Railsの宣言文はテストに値するほどの破損の原因にはならないので、カバレッジが100%よりかなり低くても、彼にとっては問題ないということです。

私は、「自信を持ってコードを変更できないのなら、テストが十分でない(あるいは、適切でない)」と返答し、「コードを変更する時に、コード自体よりもテストの変更に労力を費やしていると感じる時が、過剰の兆候」だと言いました。望ましい状態を維持するには、自分やチームのメンバーがどのようなミスを犯しがちで、問題にならないミスはどのようなものかを経験的に知らなければなりません。私自身は、自分のやり方に不安を覚えた時には、「コードの1行をコメントアウトする」アプローチが気に入っていると述べました。そこをスタート地点として、環境に入り込んで試行錯誤をしていくうちに、よりよい解決策を発見できるからです。Davidは、このチューニングは、変化の少ない生産チームと、コードを第三者に渡す必要があり、そのためにより多くのテストが必要なコンサルタントチームでは異なると感じていると言います。Kentは、テストファーストの規律を学ぶのはいいことだと考えています。それが、開発の困難な部分を切り開く、4WDのローギア並みの推進力となるからです。

ここでDavidが次の問題を取り上げました。以前は、多くの人が、コードよりもドキュメンテーションを重視していましたが、現在、彼が気にかけているのは、多くの人が、機能的なコードよりもテストを重視しているということです。これに関連して、TDDサイクルにおけるリファクタリングを軽んじる傾向も同じく問題として挙げています。これらの要因が、コードをクリアにする作業やリファクタリングに十分なエネルギーが注がれていない現状をもたらしたと言えるでしょう。Kentは、一部の本番用コードを捨てたものの、テストをそのまま維持して再実装したエピソードを語ってくれました。新しいコードがうまく動くかどうかは、テストにより分かるため、彼は、この手法を本当に気に入っているようです。ここで興味深い新たな疑問が生まれます。それは、本番用コードを捨ててテストを維持するのか、あるいはその逆なのかということですが、これについても、時と場合により答えは異なるでしょう。

テストを読むことで、コードの挙動を理解することがあると私は言いました。一方が他方よりも優れているとか、そのように思っているわけではありません。大事な点は、ダブルチェックで、双方にミスマッチがあれば何らかのエラーが判明するということです。私はDavidの意見、つまり、場合によってはチームがユーザのサポートよりもテスト環境により多くのエネルギーを注ぎ込むという悪しき傾向があることに同意します。テストというのは目的達成の手段であるべきです。ところで、コードをクリアにするのは本当に刺激的なことですが、最もテンションが上がるのは、新たな機能を追加する時です。大変に思えるかもしれませんが、コードがクリアでありさえすれば、案外簡単にできてしまいます。ただし、コードをクリアにするということと刺激を得るということには違いがあります。KentはこれをJeff Eastmanの事例で示してくれましたが、言葉で説明するには、なかなかに難しい内容でした。彼は設計を大幅にシンプル化することで勢いを得ることができると言います。しかし、新しいテスト動作の価値を説明するのは簡単でも、設計のクリーニングについての価値を語るのは難しいと感じているようです。

Davidは、設計の質を数字で表すことはできないと指摘する一方で、それでも私たちが数量化できるものに注力する傾向を挙げました。つまり多くの人が、本来であればリストの下位に属するもの、例えばテストの速度やカバレッジ、そして比率などを優先する傾向があるというわけです。これらはある意味、色仕掛け的な罠とも言えるもので、その誘惑には注意を払う必要があります。でなければCucumber(Rubyのテストツール、キュウリの意味も)がヤギを虜にし、本番用のコードよりもテスト環境が栄える結果となるのです。実際には、それらは非技術系のステークホルダー(利害関係者)と一緒にテストを書くという、架空の場所でしか役に立ちません。以前であれば、TDDを売り込むことが重要なことでしたが、既に十分な浸透を見せています。これからはその欠点を探すことが必要です。ただし、多くの場所で、TDDはいまだ台頭していないという声も聞くに及び、私はTDDが主流であるという意見には賛同していません。

参考資料

5:質疑応答

2014年6月4日

視聴者の皆さんから寄せられた以下の質問:TDDを使用したオープンソースにはどんな事例があるか? 私たちのTDDの使い方を変えるものは何か? 経験の浅い開発者にとって、TDDはどの程度有用か? にお答えします。そして、TDDの健全性に関する私たちの意見をまとめて締めくくることにします。

議事録

始めに、今回のハングアウトでは、視聴者から寄せられた質問を取り上げて、それに答える形で進めていくことを説明しました。最初にDavidが取り上げたのがMike Harrisからの質問で、TDDを使ったオープンソースプロジェクト、およびテスト主体の設計による弊害、あるいはTDDが適切に使われている事例を紹介してほしいというものです。Davidはこの質問を受けて、いい事例というものは存在しない言い、このことは、こういう類いの議論の問題点でもあると説明しました。オープンソースのコントリビュータは、プライベートアプリケーションやオープンソースの共通フレームワーク、ライブラリに取り組むことが多く、一般的にいい活用事例が見当たらないということです。結果的に、意見の相違が実際よりも多くあるかのように見えがちな文脈で話を進める羽目になります。人々が興味を持つのは、哲学的な原理よりも実際のコードです。しかし、私たちの手持ちのコード事例というのは、実際にプログラマが現場で取り組んでいるものよりも規模が小さいのは否めないため、よりよい理解のためには発表されているものを参照するほかありません。DavidがJim Weirichの例を通じて設計の弊害を示したのもその理由からです。なお、Railsで優れたコードを書きたい場合、彼はRailsの標準的なテスト手法を扱った本を勧めています。

私は、実際のコードを理解するには、相当の努力を要するとコメントしました。私自身、自分たちのコードベースを精査することがありますが、かなり時間かかる上、チームのメンバーと共同で作業する時とは違う理解に至ることさえあります。Kentは、JUnitはTDDを忠実に使い、それがうまくいったプロジェクト例だと言います。しかし、JUnitはTDDを最適にするような明確なインターフェースがあるため、この議論のいい事例とは言えないでしょう。私たちは、さまざまなアプリケーションを対象として話しているわけですからね。誰かいい事例をお持ちの場合は、ぜひそのコードを書き示していただきたいと思います。

Davidは、私のコメントが、プログラミングを科学の一種として扱えないことを表していると指摘しました。つまり、技術を客観的に評価できないということです。ただし、これは議論に値しないということではありません。確固とした答えを得ることはできないものの、その判断は自分自身に委ねられていると考えることができます。Kentは手順の再現はできないという点で同意を示しました。しかし、科学的な観点から、個人的に物事を見ることはできると言います。普遍的な答えが得られないだけで、私たちは自分自身のために、物事を実験的に試すことができるのです。

Kentが次の質問を選びました。Graham Leeからの質問で、その内容は、KentやMartin(私)に対しては、ソフトウェアの書き方の何が変われば、TDDが不必要または時代遅れになるか、Davidに対しては、TDDが有用さを発揮するためには何が必要だと思うか、というものです。Kentは、彼のRIP TDDの投稿に言及しつつ、彼自身の立ち位置を(どちらかと言えば皮肉な調子で)説明しました。TDDは、いくつかの問題を解決するもので、最たるものが自信を与えてくれる点だと言います。併せて、問題を断片へと細分化できるため、総合的な問題から離れて、特定な問題だけに取り組めるようになる利点も挙げています。現時点においては、TDDが難しいというだけで、その使用をやめるつもりはないということです。

私の意見としては、TDDを時代遅れにさせるものは、何らかの変化というよりも、それぞれのコンテキストにおけるTDDの妥当性なのではないかと思っています。例えば私は、TDDに機械的に従って作業することにより、「迅速ではあるけれども急かされないような」ストレスのない状態で、いい設計にたどり着いたことがあります。一方、最近の私は、プログラミング作業のほとんどでWebサイトのツールチェーンを使っています。その進捗は漸次的ですが、とてもいいリグレッションテスト・スイートがあるため、TDDの有効性を見つけることができません。そうかと思えば、Infodeckのコードを構築する時には、TDDが効果的なアプリケーションの振る舞いがいくらでもあります。「あるコンテキストは非常にTDDに向いている一方で、そうでないコンテキストもある」 そして、開発者の個性によっても、そのコンテキストが変わるのではないかと思います。

すると、同じような経験をしたことがあるとDavidが口を開きました。TDDを通じてテストを学んだ彼は、TDDをとても気に入り、全てのものでそれを試してみようとしました。しかし、MVCモデルのWebアプリケーションの多くの領域では、TDDが適していないと徐々に気づいたと言います。これは、ある状況においてTDDが役に立たないということを意味しているわけではありません。Davidの場合は、TDDが役立つケースが少なかったというだけです。ただ、TDDを断念したからといって、彼がコードのセルフテストを諦めたわけではありません。セルフテスト自体は、彼にとってTDDの価値であり続けています。

この話を聞いて、私はこれがTDD(あるいは何らかの技術)を、身を持って知る理想的な姿だと言いました。まずは試してみて、とことん使った上で、自分にとってちょうどいい加減を見つける。そして、さらに考察を重ね、David自身は「TDDはセルフテスティングコードへの入門ドラッグ」という結論へと至ったというわけですね。Kentは、こういったプロセスを経て、開発者がどのようなワークフローにたどり着いたか、というのはあまり気にしないと言いました。彼のTDDの経験はDavidとは異なるものです。開発のある段階で難しい局面に差しかかった時、彼は、物事を単純化できる特定のプロトコルを持つオブジェクトのアイデアを考えると言います。そして、そのようなアイデアやAPIの使用例などを実装して試す時に、TDDのメカニズムは迅速にフィードバックを与えてくれると言うのです。もちろん、TDDでうまくいかないこともあります。そんな時は、コマンドRがフィードバックを得るいい方法だということですが、それでもKentの希望としては、オブジェクトを単純化してからTDDでテストしたいということです。

私は次の(Tudor Pavelからの)質問を選びました。その内容は、経験の浅い開発者にとってTDDがどう役に立つかというものです。私の答えは次の通りです。TDDを使うことで、開発者は少しずつ作業せざるを得なくなるし、インターフェースと実装を分けて考えられるようになります。ただし、いい設計には経験が必要なため、必ずしもそのことが、いい結果に結びつくとは限りません。経験の浅い開発者がTDDを利用すると、リファクタリングが十分でないことが多々あり、最適とまでは言えない設計になることがあるのも事実です。経験の浅い開発者と熟練の開発者の作ったものでは比較の対象になりません。比べるなら、経験の浅いその開発者が、TDDなしで書き上げる場合の設計と比べてみましょう。もちろん、これは実測できるものではありませんが、とても有効な方法だと思えますし、将来的な観点では、リファクタリングを通じて改善しやすいセルフテストのコードベースを作ることにもなります。そういう意味では、TDDは開発者にとって、いい出発点となるでしょう。

Davidは、それがTDDから得た大切なものだと言いました。彼はTDDを使い始めてみて、TDDが優れた補助輪であることに気づいたと言います。ただし、それ以来は、十分に考察を進められたとは感じていないようですが。彼は次のフレーズ「新人に対してはシンプルで直接的で、かつ大げさにアドバイスを与えなければならない。さもないと彼らは何もしない」という考えに懐疑的です。それは単に、教える側の自信の欠如を表しているだけだからです。私も、その教義的なフレーズについての嫌悪感には同意します。自分の説明に対する反論を提示できない場合、自分の分析不足を疑ってしまうでしょう。1点だけ念頭に置きたいのは、新人に対しては、導入の手引きを繰り返し教え続けなければならないということですね。一部の人は、基本の繰り返しをおろそかにしがちです。

これを受けてDavidは、だからこそ、私たちはこの話し合いをしていると思う、と言いました。TDDが人に教え伝えられる時、基本的な事項に色眼鏡的な解釈が織り交ぜられることが多いという現状があります。10年後、本来ならもっといい場所にたどり着いていたはずなのに、スタート地点よりも随分と下ってしまう結果になるかもしれません。そうなればリセットボタンを押して、洗練されていなくとも効果的なアプローチで出直すしかないでしょう。Davidが「TDDは終わった」と言う時、彼が指しているのは、異質なものへと変化してしまったこの現状のことなのです。最初の原則に立ち返らなければなりません。Kentは、最初にDavidの基調講演を聞いた時の直感的な感想は、そういうものだったと述べました。プログラマは、どうしても同じことを何度も何度も繰り返し、物事を複雑にしてもなお、進行中の機能不全のシステムに固執しすぎるきらいがあります。Kentは、リブートして原則に戻ることに大賛成です。ただし、ここ10年の、人々のプログラミングに対する期待値の高まりは失いたくないと言います。自信を持ち、進化の方に向かい、生産的で技術的なコラボレーションを実現することは、きっと可能なはずです。Kent自身、キャリアのスタート時には及びもつかなかったような状態に、今の自分があると実感できているということです。

Davidは、TDDやXP、そしてRuby、これらいくつかのものが同時に起こったと言います。プログラミングは楽しくあるべきという彼の考えは、人に笑われることもありますが、Railsの展開において、彼はその考えを持ち続けていきたいようです。現状、Rubyの世界は、その幸せの謳歌を当然と見なしていると彼は考えています。つまり、Agileのように勝利したというわけです。私は、Agileが勝利したとは思いません。どちらかと言えば、名前が知れたというだけかと思います。多くの人がAgileに手を付けていると言いますが、実際はそうではないはずです。こういうことはよくあることで、私はこれをsemantic diffusion(意味の希薄化)のプロセスと呼んでいます。何が大きな勝利かというと、クライアントに対して公然とAgileを使えるようになったことでしょうか。Davidは、先ほどのリブート問題を別の見方で考察します。まず彼は、10年後には粗悪なプログラムであふれるだろうと言い、Pinkberry(フローズンヨーグルトの会社)の例を挙げて説明しました。Pinkberryの創業時にはフレーバーが2つだけたったのに、見る見るうちに他の競合企業と同じように、フレーバーの種類は複雑になっていきました。「ほとんどの人は、いいアイデアをそのままにしておくことができない」のです。TDDとAgileは、現在、広く使われていますが、Agileを使っていると言う人は、実際は逆のことをしていると言っても過言ではないのかもしれません。ただし、Kentによれば、TDDに関してはDavidが指摘したようなことは彼自身、経験しておらず、最初の原則から離れることなくTDDを適用していると述べました。

ここでKentがDavidに対して、感謝の意を表します。TDDの周りにフジツボが固着しつつあり、それをこすり落とす必要があることを彼が取り上げてくれたからです。Davidは、Railsでも似たような問題はあると言い、基本の形態でRailsを使う彼には、驚くような使用例がいくつかあったことを述べました。Kentは、XPが注目された最初のOOPSLA(ACMが毎年開催する国際会議)において、Jim Rumbaughが語った「10年後にXPがどうなっているかは、誰にも分からない」という発言に言及し、それが正しかったことを改めて確認します。それを受けて、私は「これが成功の形で、そうでない場合、物事は軌道に乗らない。その技術に固有の弊害、または誤用によって、どんな悪いことが将来的に起こるかなんて、誰だって予言するのは難しいものだ」と続けました。私たちができることと言えば、基本や教訓を忠実に繰り返し続けるということのみです。Davidは、「英雄となって死ぬか、悪人となるか」と言い、私の発言に同意を示します。Rubyは、プログラミングの優れたアイデアをリブートして作られたものです。そして、関数型言語も別の形のリブートと言えるでしょう。これらのリブートは健全です。Davidは、RailsやTDDの寿命の長さに驚いています。彼がRailsの前に使っていた言語はPHPで、適切に使えば非常にいいコードを書くことができますが、彼にとっては他の言語の方がもっといい使い方ができると思えるものだったようです。その彼も、MVCモデルのWebアプリケーションでTDDをうまく使うのは、PHPでクリーンコードを書くよりも難しいと言います。

これに対してKentは、小さな問題を実用的に抽象化できる時には、常にTDDが適用できると反論しました。彼は、プログラミングに全自己を投入するという大きなゴールへと至る他の道も模索したいと考えています。そして、今後もさまざまな実験(過剰、または不十分な実施、安全地帯の探索、そして理由の理解)をしながらこの探求を続けるようです。彼は「TDDは終わっていない」と言ってDavidの意見に真っ向から対立しながらも、Davidが火を付けてくれたおかげでTDDが不死鳥のようによみがえったことには感謝しています。

ここで、この議論を始めた理由をDavidが説明しました。それは、多くの開発者が、TDDが有効でない事例を話したがらないということです。TDDはマストで、公に非難しにくい状況があるというわけですね。彼は、そういった反応の受け皿を作れば、TDDが適切な場合と不適切な場合についての議論ができるのではないかと考えました。インターネット上では多くの人がTDDの有用性を語っていますが、欠点の指摘などは見かけることがありませんからね。ただしDavidにとっては、TDDを批判したとしても、セルフテスティングコードは大事なものであり、それだけは欠かすことができないもののようです。

最後に私が結論をまとめました。私たちには(当初、予測したように)お互いに同意する点が数多く存在します。例えばセルフテスティングコードの重要性、そして一定のコンテキスト下におけるTDDの有用性の認識。ただし、コンテキストの数(どのコンテキストでTTDが不適切かを挙げること)については意見が食い違うかもしれません。いずれにしろ、全てはある一点に集約されると思います。ソフトウェアの開発に関わるのであれば、それについて思慮深く考え、技術をやみくもに選ばずに、自分やチームにとって何が機能するのかという判断に基づいてワークスペースを構築しなければならないということです。まずはそのテクニックを試し、実際に使って、とことん突き詰めることで、その有効さを見極めてください。私たちが属しているのは体系的な科学の領域ではありません。自分たちの経験がものを言う世界なのです。