2007-07-20

テスト駆動開発やユニットテストを定着させるには

 ユニットテストやテスト駆動開発(TDD; Test Driven Development)については、メリットや作り方の説明ばかりが先行している気がします。
現場にとって本当に必要なのは、どうやったら開発の現場に定着させることができるのかということではないでしょうか。

カバレッジだとか、より効果的なテストコードだとかは、ユニットテストが継続的に実施されるようになってからのハナシだと思います。

私が実際に、開発現場にユニットテストとテスト駆動開発を導入した経験から、この問題を考えてみたいと思います。

コンテンツ

  1. 障壁
  2. ユニットテストしたくなる環境
  3. オススメ

障壁

まず、最初に意識しておいていただきたいのは、テスト駆動開発を導入する際に、障壁となるのは誰かということです。
テスト駆動開発の導入をもっともシブるのは、経営者でもマネージャでもありません。
技術者です。
普通、技術者(に限らないとは思いますが)というのは、理由もなく何かを拒絶したりはしません。
ただ、その理由をうまく表現できない方もいらっしゃいます。
多くの技術者は、工数を増大させるものや、実現を難しくするものなど、ヤバいものを嗅ぎつける嗅覚を持っています。
でも、それはただクサいのであって、なぜクサいのかまでは、本人にもはっきりわからないという場合もあるようです。
ですから、本人達から理由を聞き出すことは、難しい場合もあるでしょう。
しかし、そこにはちゃんと理由があります。
効果的なテスト駆動開発を経験したことがない技術者がシブるのには、以下のような、もっともな理由があります。

コーディング量が増えるたしかにそのとおりですね。
テストコードも立派なコードですから、コーディング量はかなり増えることになります。
効果的なユニットテストを実施するためには、テストコードは、少なくともテスト対象のコードと同程度の量になるでしょう。
つまり、コーディング総量は倍に増えるわけです。
しかも、テストコードは本番の動作には影響しません。
実際のアプリケーションと同じだけの量のコードを作って、しかもそれがアプリケーションには含まれないのですから、気乗りしないのももっともでしょう。
ユニットテストを使わない、人力によるテストとデバッグに、実際にどれだけの工数を使っているのかを、一度計測してみるといいかもしれません。
私も、実際に計測したことはありませんが、ちょっと予測してみて下さい。
驚くべき数字が見られると思いますよ。
Code Complete第2版〈下〉によれば、デベロッパーテスト(単体テスト、結合テスト、統合テスト)は、プロジェクトの規模や複雑さに応じて、全体の8%~25%が費やされるべきなんだそうです。
さらに、デバッグは、開発時間全体の50%にも及ぶ場合があるとも指摘されています。

テストコードも人間が書くのだから、人力テスト用のテスト項目と比べて、品質が高くなるとは思えない
たしかに、ユニットテストのテストコードも、しょせんは人間が作るものです。
また、テストコードはプログラミング言語で書かれます。
ですので、自然言語で書くテスト項目と比べて、むしろミスをしやすいでしょう。
テストコードを書いて最初の頃のテストは、あまり品質は期待できません。
ですが、これはテスト駆動開発にとって、あまり問題ではありません。
テスト駆動開発では、クラスを作る際、実際のコーディングに入る前にテストコードを書きます。
テストコードを書くという行為は、これから作るクラス仕様を明確にするということなのです。
テストコードは、動く仕様書なのです。
メソッドが一つできるたびに、そのメソッドが仕様を満たしているかどうかを、テストします。
クラスが出来上がるまでには、全てのメソッドがテストを通過しています。
逆に言うと、全てのテストコードもテストを通過しているということになります。
テストでエラーが発生した際には、テスト対象のコードと共に、テストコード自体も疑われ、妥当性がチェックされます。
ですので、テストを通過したテストコードは、チェック済みの妥当なテストであるということになります。
プログラムが仕様に準拠しているのか、自分自身で検証してくれる仕様書なんて、今までありましたか?
動く仕様書を手に入れることの意義は、非常に大きいものです。
これを動かせば、プログラムのどこが仕様に準拠していないのか、明確に示してくれるのです。
ところで、プログラムが稼働を開始した後になっても、完璧にメンテナンスされた仕様書なんて、これまではほとんど存在しませんでしたよね。
少なくとも、私は一度も見たことがありません。
チェック済みになった動く仕様書は、常にプログラムと自分自身が同期がとれていることを保証してくれます。
テストコードは、単に動くというだけでなく、同期がとれていることを保証してくれる仕様書なのです。
さらにいえば、テストコードは仕様書であると同時に、サンプルコードです。
テスト対象のクラスの使い方は、テストコードを見れば一目瞭然です。
テストコードは、正しいことがチェック済みなのですから、これほど頼りになるサンプルコードはありません。

ユニットテストしたくなる環境

テスト駆動開発を現場に定着させることは、関係者を説得するだけでは難しいようです。
技術者の意識にある障壁を取り除くだけでは、不十分なのです。
私も、最初は懸命に説得する努力をしていました。
その甲斐あって、開発にユニットテストを導入することはできましたが、プロジェクト期間の半分も維持することができませんでした。
これでは、まったく意味はありません。
なにがマズかったのでしょう?
その開発チームには、ユニットテストが効果を発揮するための環境がなかったのです。
効果が出ないなら、やろうという気もなくなってしまいます。

どんな環境?それは、ユニットテストが継続的に、そして自動的に行われる環境です。
必要なのは、専用のマシン上で、(なんでもいいんですが)CVSなどの構成管理システムを使用すること。
そして、構成管理システムにソースがコミットされるたび、あるいは数分おきに、自動的にソースをチェックアウトし、ユニットテストを実行するシクミです。
専用のマシンは、テストをするだけのためのものですので、それほど高い性能は必要ありません。
また、定期的に自動テストを実行するシクミを作るのは、そう難しいことではありませんよね。
みんな、ソフトウェア開発のプロなんですから。
なぜ、こんな環境が必要なのでしょう?
鍵は、バグが検出された時期と修正コストには、深い関係があるという事実です。
バグの修正コストは、検出されるまでにかかった時間の長さにともなって、急激に増大します。
  • バグが埋め込まれたクラスを利用するクラスが、間違った動作に基づいて、誤って実装されてしまうなど、他のコードへの悪影響
  • プロジェクトが進行し、実装済みのコードが増えれば増えるほど、バグが埋め込まれたコードが、その中に埋もれてしまうこと
  • 時間が経つことで、バグを埋め込んだ本人の記憶が薄れ、バグの箇所や原因を思い出すチャンスが失われていくこと
などにより、正しく修正することが難しくなります。
修正自体に時間がかかるだけでなく、間違った修正をしたり、修正によって新たなバグを埋め込んでしまうリスクも増えるのです。
こうして、修正コストは刻々と増えていきます。
できる限り頻繁にテストし、バグを即座に捕捉することで、修正コストを最小限に抑えることができます。
少なくとも1日1回のビルドが必要ですが、もっと頻繁にできるなら、そうするべきです。
どれくらいの頻度でビルドできるかは、アプリケーションの規模によります。
つまり、そのアプリケーションのビルドにかかる時間が、限界の頻度ということです。
ビルドに5分かかるのなら、最大でも5分おきというのが、限界頻度ですね。
念のために1分ほどのバッファを用意して、6分くらいにするのがいいかもしれません。
そうすると、バグが混入したコードをコミットした場合、遅くとも6分後には、警告を受け取ることができます。
要するに、バグが検出されるのは、早ければ早いほどいいのです。

その環境に、どんな効果が?頻繁にバグが検出されることで、担当者が修正に追われ、新しいコードを書く時間と集中力を、すっかり奪われてしまうのではないかという不安を感じますか?
人力でのテストを頻繁に繰り返して行うと、そういう事態に陥ることがよくありますね。
でも、テスト駆動開発を行っている場合、実際には、その逆の結果になります。
もしバグを埋め込んでしまった場合、それはコミットすれば即座に検出されますので、担当者がバグの警告におびえるのは、コミットした直後だけになります。
ここでバグが検出されてしまった場合だけは、担当者は他の全ての作業を中止して、バグの修正を行わなければなりません。
こうすることで、構成管理システムにコミットされたコードを、常にクリーンに保つのです。
しかし、ユニットテストをクリアしてからコミットしているのであれば、バグの警告を受けること自体が稀なはずです。
これらのことが、困難でストレスの溜まる作業である、テストとデバッグを大幅に軽減するのです。
また、既にお話しした「テストコードは同期を保証する仕様書である」ということもポイントです。
常にテストを実行することで、継続的に同期が保証されます。
開発者は、常に正しい仕様書というよりどころを持つことができ、安心して開発することができるようになります。
「正しい仕様書がないから、自分がいじったコードが正しいかどうかに確信が持てない」という不安な状況は、もうなくなるのです。
たったこれだけの手間で、コミットしたソースの妥当性が常に保証されるようになります。
これは、一旦コミットしたソースは、もうテストする必要がないということを意味します。
何か不都合があれば、システムの方から勝手に知らせてくれますから。
開発者は、多くの面倒な再テスト、いらいらする再デバッグの手間から解放されるのです。
それだけでなく、構成管理システムによって、常に正しく動作するソースがあるという大きな安心感を持って、積極的に開発をすることができるようになります。
何か間違ったことをしてしまっても、いつでも正しいソースに立ち戻ることができるのですから。
赤ちゃんって、いつでも母親の懐に戻れるという安心感をバックに、どんどん新しい領域へ行動範囲を広げていくものです。
開発者も、いつでも正しいソースに戻れるという安心感をもって、チャレンジを繰り返していくことができるのです。
これらの効果は全て、構成管理システムとテストの自動化があってはじめて発揮されるものです。
ユニットテストが作れても、自動的に実行されるのでなければ、こういった効果は期待できません。
人間がテストをスタートさせるのでは、それほど頻繁にテストを実行することができませんし、きちんと定期的に実行されるかどうかすら、怪しいものです。

だから技術者は、効果がなければ、それを敏感に感じ取ります。
そして、ムダなことに労力を注ぐのは、すぐにやめてしまいます。
あなたも技術者なら、そうでしょう?
開発現場にテスト駆動開発とユニットテストを根付かせるためには、口で説得するだけでなく、まずは環境を作ることが必要なようです。

オススメ

 達人プログラマー―ソフトウェア開発に不可欠な基礎知識には、今回お話ししたユニットテスト構成管理自動化の効果的なやり方が、詳しく解説されています。
この記事では、まず環境を作りましょうという論点をぼかさないために、いろいろはしょった話題があります。
そういったことについても、この本を読めば、きっちりおさえることができるのではないでしょうか。
この本は、バージョン管理、ユニットテスト、自動化というふうに、それぞれのトピックが3つの部に分かれています。
しかし、今回お話ししたように、これらが密接に関連することで、テスト駆動開発の環境を構成しています。
この本を読むときには、各部を別々に考えるのでなく、関連を頭に描きながら読んでみて下さい。
自動化については、この本で紹介されているフリーのツールを使用すると、ものすごく簡単に実現することができます。
しかも、テスト結果が即座にWebページ上に反映され、そこで詳細な情報を確認できたり、RSSやEメールで結果を受け取ることができたりと、けっこう便利です。
Webサイトからは、この本で紹介されているものより新しいバージョンのものがダウンロードできますが、この本の指示とあまり変わらないやりかたで使えます。
また、Code Complete第2版〈下〉では、テストやデバッグについての包括的な知識や、細かいテクニックが詳しく紹介されています。
この本によれば、デバッグにかかる所要時間は、非常に個人差が大きく、20 : 1もの開きがあるのだそうです。
さらに、より多くの欠陥を見つけ、より正確に修正するという点でも、個人差があります。
「デバッグは開発時間全体の50%にものぼることがある」という指摘と考え合わせると、この差は深刻と言ってもいいくらいです。
テストやデバッグについての正しい知識と、優れたテクニックを身につけることは、あなたの開発力を大きく向上させるのです。

6 件のコメント:

  1. とても興味深い記事ですね!
    恐縮ですが、私の個人的な考えを少し述べさせて頂きます。
    ユニットテストは特に指示されていなくても、個人が必ずやっておく事の一つとして捉えています。
    ユニットテストと言っても、仕様書を作成して実施するという、大袈裟なものではなく、デバッガか何かで、全分岐テストをするようなものです。
    その際に、”自分の身を守る手段”として、テスト駆動開発は有効だと感じています。
    一度、テストコードを書いてしまえば、設計バグや仕様変更が行われた際に、非常に楽にテストができるからです。
    以上、私の独り言でした。
    そして、ここからが本記事への感想となります。
    プロジェクトチームに、テスト駆動開発を定着させる方法として、ソースのコミット時に、自動でテストコードを実行させる環境を作るという事ですが・・・。
    なるほど、お見事ですね!
    この方法だと、仮にプロジェクトメンバー全員が入れ替わったとしても、この環境は残りますから、今後の保守でバグ対応や仕様変更が行われようとも、テスト駆動開発を前提とした開発を維持していけるわけですね。
    随分昔の話ですが、4年前・・・。
    私は社会人1年生の初プロジェクトでの事です。
    そのプロジェクトでは、テスト駆動開発が導入されていました。
    その頃は、テストの意義もよく理解していませんでしたの、よく意味が分からなかった事を覚えています。f(^_^;
    この時は、各々にテスト駆動開発を実施するように徹底していただけでしたから、ズルする人が、いてもおかしくない環境だったと言えますね。
    以上、長々とコメントを書いてしまい申し訳ありません。
    とても、面白い記事でした。
    次回も楽しみにしています!m(_ _)m

    返信削除
  2. 今回も感想をお寄せいただいて、ありがとうございます。
     自動化を行わずに、ユニットテストを徹底するというのは、不可能ではないにしろ、辛く険しい道だと思います。
     品質よりも、目先の時間短縮にインセンティブが向いているというのが、多くのソフトウェア開発の現場の実情だと思います。
    そんな状態では、テストの意味を充分に理解していてすら、徹底は難しいものです。
    まして、メンバー全員が意味を熟知しているなんて恵まれた状況は、なかなかありませんからね。
     命令での徹底というイバラの道を歩むより、"そうしたほうが楽になる"ような環境を整えた方が、うまくいくことは、いろいろあると思います。

    返信削除
  3. はじめまして、
    TDDの現状について検索している中で立ち寄らせていただきました。
    非常に興味深く記事を読ませていただきました。
    私は今、アーキテクトとして、ASP.netの新規開発の開発プロセスを準備しているものです。
    テスト仕様書からテストクラスを出力するツールを作成してみようと考えています。
    経験上コーディングスピードが速い技術者ほど、開発生産性に執着し、TDDには乗り気でないことが多いと感じています。
    が、初回開発で削減できるコストはプロジェクト全体からみれば小さなもので、TDDを行うことで削減できる単体テスト、結合テスト、品質管理、拡張・変更時のリグレッションコストなどのコストやリスクはそれを圧倒的に凌駕することは明確だと思いますので、品質管理の観点をTDD導入と同時に主張していこうと思います。
    他の記事も読ませていただきます。

    返信削除
  4. はじめまして、まっつんさん。
     そのツールで、テストクラスを作る手間が小さくなれば、TDD導入への反発もそれだけ少なくなるかもしれませんね。
     確かに、コーディングスピードを重視して、それに磨きをかけてきた技術者は、そこに誇りを持っていますので、スピードを鈍らせるものには否定的になりがちですね。
     会社や上司の評価などによるインセンティブが、品質向上による、プロジェクト全体を通してのコストダウンよりも、コーディングスピードアップによるごく一時的なコストダウンの方に向いているケースが多いのが、大きな原因だと思います。
     現状への解決策として、自動生成ツールはいいアイデアだと思います。
    それに加えて、もう少し長い目で見るなら、技術者へのインセンティブ―動機づけの方にも、目を向けてみられてはいかがでしょうか。

    返信削除
  5. 「テストコードは、動く仕様書なのです。」
    との事ですが
    テストコードが正しい仕様であるかをチェックする必要があると思います。
    ��ユーザーの意図しないテストコードでテストして、
    ��間違ったプログラムをつくってしまう。
    そのためには、すべてのテストコードをレビューする必要があるでしょう。
    『テスト駆動開発』や『ユニットテスト』の議論では、上記のコストが無視されていると
    思います。

    返信削除
  6. はじめまして、はてなさん。
     冷静な視点で、多角的に、また客観的に状況を捉えてコストを判断することは、常に大事なことですね。
     もちろん、テストコードが正しく動くかどうかだけでなく、テストの「意図」自体が正しいかどうかを評価することは必要です。
    そして、評価する必要があるということは、コストがかかるということです。
     しかし、このコストはユニットテストを行ってさえいなければ、払う必要のないものなのでしょうか?
    ユニットテストを行う場合には、テストコードをレビューすることで、各クラスの実装意図を、実装方法の詳細とは切り離したところで議論する機会を得ることができます。
    しかし、もしユニットテストを行わないのであれば、同じことを違うタイミングで議論する必要があるはずです。
    おそらく、それは詳細設計レビューか、コードレビューになるのではないでしょうか。(どこでレビューするにしろ、ペアプログラミングを導入することで、ある程度レビューのコストを抑えることもできると思いますが、それはまた違う議論ですので、ここではそのことは考えないことにします)
     私自身の意見としては、詳細設計レビューや本番コードのレビューと、ユニットテストのレビューは、分けて考えたほうがよいと思っています。
    実装で、必ずしも詳細設計そのままの写像が作られるのではないことが多いため、詳細設計のレビューでは、テストコードをレビューする場合と比べて、実装上の問題点については発見しにくくなります。
    逆に、コードレビューでは、実装方法の詳細を見ていくことになりますので、こまかい「方法」に議論が集中し、「意図」については盲点になりがちです。
     誤解しないでいただきたいのですが、ここで私は詳細設計のレビューやコードレビューがいらないと言っているわけでも、軽視すべきだといっているわけでもありません。
    私が言いたいのは、詳細設計レビュー、あるいはコードレビューの中でいっしょに行われている、「インタフェースの意図」という観点について、独立して考えたほうがいいのではないかということです。
     「すべてのテストコードをレビューする必要がある」というお言葉から推察して、おそらく本番コードについては、現状すべて、あるいは大部分のコードをレビューしておられるのだと思います。
    実際、すでに「レビュー」という作業には、かなりのコストがかかっています。
    これ以上レビュー対象物が増えると困るとお考えになるのも、よくわかります。
    しかし、テストコードのレビュー対象は、テストコード自体ではありません。
    レビュー対象は、あくまで本番コードのインタフェースです。
     ですので、テストコードのレビューは、今までやっていなかったまったく新しい作業ではありません。
    コストはかかりますが、それは今までだってすでに払っていたコストであり、新たなコストが発生するわけではないのです。

    返信削除

Related Posts Plugin for WordPress, Blogger...