【しばらく編集不可モードで運営します】 編集(管理者用) | 差分 | 新規作成 | 一覧 | RSS | FrontPage | 検索 | 更新履歴

FlawedTheoryBehindUnitTesting - 単体テストに潜む誤った理論

目次

単体テストに潜む誤った理論

この文書について

単体テストに潜む誤った理論

私は Google の blogsearch 一式を使って単体テストに関する話題を拾っている。 普段は一週間に数十の blog やメーリングリストの議論に目を通す。 新しい話題もたまにはある。けれど、多くの話題は繰り返しだ。同じ主張が何度も現れる。 その中でもひときわ私を悩ませる、単体テストの話題がある。 それが悩ましいのは、件の主張がテストと品質に関する誤った理論に基いているからだ。 その主張は私がずっと前に諦め、お引き取り願ったものだ。この blog があるのは幸いだった。 まず、ちょっとした歴史の話をしたい。

2000 年代初頭、私はあるカンファレンスで Steve Freeman と話をする機会があった。 テスト駆動開発についての話をした。Steve には、はっきり感じていることがあった。 当時 TDD を実践している人のほんどは間違いを冒している ... 何かを欠いているのだと。

Steve は XP や TDD を黎明期から実践しているある緊密なコミュニティの一員だった。 今もそこにいる。彼らから生まれたのがモックオブジェクトという概念だ。 Steeve Freeman と Tim MacKinnon? はその概念をより広範なコミュニティに紹介する記事を書いた。 あとは知ってのとおり。今やほとんどの言語にモックオブジェクトのフレームワークがあり、 日常的に使われている。

ただ、モックオブジェクトは TDD の中でもどちらかというと 広く公知されていないアプローチのひとつだ。 私の聞いたところによると、Connextra というスタートアップの CTO である John Nolan が 開発者に指示した、こんな難題が事の始まりだという: オブジェクト指向のコードを getter なしで書け。 他のオブジェクトには、できる限り尋ねる(ask)のではなく頼む(tell)ようにすること。 このプロセスに従うこと、コードが柔軟で変更に強くなることに開発者は気付いた。 彼らはまた、自分達のつかう fake オブジェクトを書く作業が繰り返しばかりなのにも気付いた。 そこで期待した挙動を設定できるモックのフレームワークという考えに至り、 モックオブジェクトが生まれたのだ。

Steve からこの話を聞いたとき、私はそれは良さそうだと感じたものの、 思い至らなかった点がひとつあった。 Steve や Tim, そのチームの人々は, モックを大々的に使っていたのだ。 実際、彼らは使えるところではどこでもそれを使っていた。 これは私たちが TDD を実践する時のやりかたとは少し違う。 普段私がやるのは、テストを使ってクラスを動かすことと、 そのクラスが肥大化してきたら新しいクラスを括り出す(extract)することだ。 テストのいくつかは一つのクラスだけを扱う。 また、一緒に動く複数のクラスを含むテストもある。

私がモックオブジェクトの方法に見出したのは、それが独立したクラスだけをテストし、 クラス間の対話をテストしないという問題だった。 たしかに私の書いたテストは名目上単体テストだけれど、 それが時折クラス間での本物の対話をテストする、その事実は気に入っている。 そう、私は分離(isolation)が好きだ。けれど、この爪先だけの統合レベルテストは、 私のテストに少しの力と少しの強さをもたらしている。 しかし、ここに一つ問題がある。 広範にモックを使う Connextra のチームは極めて低い障害率を報告しているのだ。 彼らがどうやって良い結果を出したのか、私にはさっぱりわからない。 そもそも、彼らが何らかの統合テストをしている様子はなかった。 彼らのアプリケーションは統合エラーに溢れるはずだ。あるいはテストがあるはずなのか? 私達の推理を披露しよう。

単体テストに関する非常によく知られた理論の一つに、テストが捕捉したエラーを除去することで 品質が良くなるというものがある。一見、これは理にかなっている。 テストは成功するか失敗するかで、失敗すると私達は問題があったとわかる。そして問題を正すことができる。 この道理に従うなら、統合テストをすれば統合エラーは減るはずだし、 単体テストをすれば「単体」エラーが減るはずだ。これはよくできた理論だが、間違っている。 間違いを知るには、単体テストとその他の品質改善手法を比べてみるのが一番いい。 計測で劇的な成果をあげた方法を調べてみよう。

1980 年代当時、クリーンルームのソフトウェア開発と呼ばれる手法を用いた活動があった。 クリーンルームの背後にある考え方は、開発の厳密さを高めることで品質を高めるというものだった。 クリーンルーム開発では、開発者が書く小さなコード片ごとに論理述語を書く必要があった。 また開発者はレビューを通じ、コードがその述語以上でも以下でもないことを示す必要があった。 これはとても厳格な手法で、更にいま書いた以上に先鋭的だった。 もう一つの特徴として、クリーンルーム開発には単体テストがひとつもないのだ。無。ゼロ。 レビューのあとにコードを書いたら、それを正しいとみなす。唯一のテストは、 機能レベルでの確率的なテストだけだ。

驚くべきことに、クリーンルームはうまくいった。クリーンルームのチームは 極めて高い品質指標を実証してみせた。その話を読んだ私は愕然とした。 けれどそのプロセスに関する本を読む中で、私はある一節に出会った。 著者はこういう。多くのプログラマはコードの一部を書いてから述語を書く。 しかし経験を積んだプログラマはまず述語を書くのだと...うん、馴染みのある話じゃないか? TDD ではまずテストを書く。本質的に、そのテストはこれから書くコードの振舞いに 関する仕様になっている。

ソフトウェア産業の中で、私達は何年も品質を追い求めてきた。 興味深いことに、うまくいく方法はいくつも知られている。 「契約による設計」はうまくいく。「テスト駆動開発」はうまくいく。 「クリーンルーム」、コードのインスペクション、高レベルの言語もそうだ。

こうした技術はどれも、それを使うことで品質があがることを示してきた。 そして、注意深く見れば理由はわかる。こうした手法のどれを用いても、 自分が書くべきコードについて熟考せざるを得なくなるのだ。

これは魔法の力であり、単体テストも同じ理由でうまくいく。 単体テストを書くとき、 TDD スタイルにせよ開発してから書くにせよ、 書き手は慎重になり、考え、テストが失敗するまでもなく問題を防ぐことも多い。

さて、ここまでを読んだ人はこう考えるかもしれない。 何もせず椅子に深く腰掛け、顎の上に頭をおいて、コードのことを考えればいいのかな。 それは違うと思う。 人によっては、短期間ならその方法でうまくいくかも知れない。 けれどソフトウェア開発は長期戦だ。 継続的な規律、絶え間ない熟考の姿勢を支えるプラクティスが欲しい。 クリーンルームや TDD は、二つの間に大きな違いがあるとはいえ、 どちらも自らのなすことに対する確かで綿密な思考を、私達に強いるのだ。

どちらにせよ、たとえばクリーンルームを使っても、チームはうまいくだろうと私は信じている。 けれど TDD で書きあげたテストにはさらなる効果があり、個人的にはそれが気に入っている: テストはコードの変更を容易にしてくれる。 コードベース全体を考えなおさなくても、それがちゃんと動いているとわかる。 もし頻繁にコードの変更を加えるなら、毎回全てを考えなおすのはベストな時間の使い方ではない。 自分がテストを書き、そこに考察を記録として埋め込み、好きなときに動かせるなら尚更だ。 そうしたテストがあるなら、過去に行った他の部分の考察を繰り返す作業から自分は解放される。 終わりのない考察の繰り返しは必要なくなるのだ。

まあ、TDD の話はこんなところ。

結局、テストを機械的なものと捉えることはできない。 単体テストは単体レベルのエラーを捕捉するだけで品質に寄与しているわけではない。 統合テストは統合レベルのエラーを捕捉するだけで品質に寄与しているわけではない。 真実はもっと繊細なところにある。品質は思考と内省の賜物だ ... 緻密な思考と、慎重な内省の。 ここに魔法の力がある。常にその規律を強いる技術が、品質を高めてくれるのだ。