知人のblog記事ながら。
[TDD]id:t-wada先生のつぶやきメモ(ただぶろぐ)の内容に全面的に賛成。
TDDの是非はともかくとして。
TDDが好きな人は、テストを書きながら対象のクラス・メソッドの仕様を設計しているし、メソッドのインタフェース仕様(インプット・アウトプット)を決定している。
体感的には、テストを記述しているときにこそけっこうぐだぐだとテスト内容を見直したりテストの構成を変えたりと試行錯誤を繰り返すけれど、実コードはせいぜいリファクタリング程度で、あまり大きな見直しはそうそう起こらない。
だから、コードファーストで書くよりはコーディング時間は確かに長いが、本人の開発リズム感はけっこういいテンポになっている。リズム感の充実は快感に変わる。
TDDが嫌いな人は、TDDを押し付けられるとまずいつも通り頭の中で仕様を考えてしまう。
その後テストコード書きに移る律儀な人は、必ず「わかっている仕様を記述しているだけ」という退屈感を感じ、実コード書きにすぐにでも移りたくなる。これが開発者自身のリズム感をとにかく崩しまくる。リズム感の崩壊は苦痛に変わる。
開発者がまず第一に考慮しなきゃいけないのは、このコーディングリズム感であり、リズムが崩れるとコードが崩れる。
なので、TDD主義者ながらあえて、TDD脳を培うことのないTDD押し付けには反対したい。
これだけではなんだから、もうちょっと深く掘り下げてみるかな。OO限定で。
インタフェース仕様について考察
インタフェースを制するものはプログラミングを制する。関数型言語など、このインタフェース記述の塊に過ぎない(だから関数型言語は「正しい」んだ)。
という事は、クラス・メソッドの仕様を考えるうえでは、このインタフェースを固めてしまえば8割がた仕様が決まってしまうものだ。
インタフェースの捉え方については、IBMのクリーン・ルーム手法や仕様記述言語であるZ記法などが詳しい。
ここでは荒いながら、数点ほど。
インプットに注目する。
- 引数
- インスタンス変数
- クラス変数
- 外部入力物(ファイル・DB等)
このうち、どの情報を使っているかに注目する。(ちなみに、上から順に「危険度」が上がる。その点も注意が必要)
その許容値について考える。
- 選択値がありうるか?ありうる場合はテストしておいた方が無難。
- 境界値はあるか?あるなら上下境界値はテストしておくべきだ。
- 異常値はあるか?あるなら代表的な異常値をテストしておこう。
この時、引数が別のオブジェクトの場合は「基本」オブジェクトの状態には感知しない方が良い。
内部的にGetterにより値を得て何かを判断している場合、その部分がじつは使用オブジェクト側にメソッドが足りていない場合がほとんどだ。(まあ、「そうは言っても」という灰色な境界もあるので、あくまでも判断基準にする程度だけれど)
不思議なもので、境界値に注目していくと、境界値が問題にならないプログラミングを模索するようになる。
アウトプットに注目する。
- 戻り値
- 発生例外
- インスタンス変数
- クラス変数
- 外部出力物(ファイル・DB等)
どれも、インプットと対になっている事と、インプットに対しどのような結果になるか、が重要だ。
また、アウトプットのうち3種類以上を気にしなければならないようなメソッドはなかなか「臭い」メソッドだ。メソッドの内包や別のクラスの隠蔽などが考えられる。
注意!!これはあくまで心の中の指標に留めて欲しい。まず避けなければならないことは「開発者がTDDに飽きてしまう」ことだ。
そのためには、あらゆる機械論を退けなければならない。
カバレッジ・テストの網羅率はなぜ重視すべきではないか
「テストされていないコードは正常には動作しない確率が高い」この命題は正しい。だけれど、「C2レベルで網羅率100%のコードは正常に動作する」の命題については、問題点が残る。
- 正常な状態のコードには、必ずフェイルセーフ・コードが存在する(絶対通らない分岐条件、絶対発生しない例外対策など)。
カバレッジを重視しすぎると、これらのコードを削除せざるを得ない。すると変わりに、不適切なdefaultやelse節が発現しやすくなる。 - インタフェースのうち選択値に関しては、コードに現れない選択値もある。
TemplateMethodパタンを使って処理切り替えを実現している場合など「条件分岐」に含まれず、各実装クラスは別テストでテスト済みだとカバレッジも上がるという現象がおこりがちだ。 - カバレッジ・テストを重視すると、開発者はカバレッジ・テストを通るようにコードを書くようになる。
そうなると上記の理由で、変化への対応に弱いコードが大量生産される土壌になる。
誤解しないで欲しいが、カバレッジは「正常系の網羅程度」を調べるにはいいテストだし、それを視覚化してくれるツール(djUnitなど)はいいツールだ。それに、カバレッジを通すことで、気づいていなかったメソッドの仕様に気づかされることもある。
あくまでも「網羅率」というマジックナンバーに捕らわれてはならないという事だ。
UnitTest嫌悪者の存在
最後に、TDDの対極に「UnitTest嫌悪者」達がいる。
彼らは一様に古き悪しきウォーター・フォール開発やRAD開発の被害者だ。
これらの開発では往々にして開発期間が厳しい。厳しい開発期間で犠牲になるフェーズをみると、だいたいこの順に期間を削られている。
- 単体テスト
- 結合テスト
- 実装
また、これらは保守フェーズに移った場合にも考慮されなかったりする。
そんな期間の保障がない中でギリギリの開発をしていれば、自然と「余計な事はやりたくない」となる。
また、一番に削られるのはだいたい単体テストであるため、そんな中でUnitTestと言っても「やらなくてもいい事をやらされている」気分になりがちだ。
そしてじつにやりがちなのが、「UnitTestで書いた同じものを『単体テスト仕様書』に起こさなければいけない」という二重苦だ。これがまた、官僚主義が高い開発現場だと「まず書類から」という文化があるため、手に負えない。
UnitTestを効果的になるなら、ただツール・フレームワークを導入するのではなく、プロジェクト内にTDD脳とTDD文化をつくり出す気で本気でとりかかるべきだ。その成果は開発者自身も驚くほどに高いのだから、本気で取り組む意義は十二分にある。
彼らのような次なる犠牲者をつくり出さないためにも。