最近、TDDのテストコードは捨てても良いかみたいな議論を見ました。
これに対する自分個人の経験上の意見ですが、TDDは雑多にテストコードを使い捨てても効果を出せると思います。
もちろん、TDDで保守性が高く価値あるテストを書いて、捨てずにCIや中長期的なリファクタリングで再利用していくと、TDDの効果を増幅できます。ただ、それをするにはスキルや事前の工夫、労力が必要ですし、できる場面に限りがあります。
そういったことをやらず、もっとゆるい姿勢で取り組んでも、費用対効果をプラスにできる手法がTDDだと考えています。
今回は、そのTDDでゆるくしてもよいポイントを、実経験からまとめたいと思います。
TDDのテストは使い捨てでいい
TDDのテストはプログラマのこまごまな課題に応じて累積的に作られるため、保守コストがかかるテスト・保守する価値の低いテストが生まれがちです。そのためテストの使い捨ての発生はむしろ自然な流れです。
またTDDのテストはプログラマの不安を解消するといった軽い動機付けで作ってよいので、ゆるい動機でテストを書いて、ゆるい判断でテストを捨てていくスタイルでも、TDDは効果を出せます。
【発展的内容】もちろんTDDで再利用し続けられるテストを増やして、CIなどに組み込んでいけると、TDDの費用対効果を増幅できます。ただそれをするにはテストの保守性確保(テストコードの再整理等)、有効なテストを作るテスト設計スキル、テスタビリティの確保の労力・スキルが必要です。さらに適用できる場面にも限りがあります。そういったものは、個人でTDDをうまく回せるようになった次のステップから手掛けてみても大丈夫だと思います。またそれができるのは一部のみという認識も大事です。
TDDのテストの網羅性は気軽に主観で決めていい
TDDでのテストの十分性・網羅性は、主観でざっくり判断してよいです。例えばプログラミング中に不安を感じたら、主観で不安がなくなったと感じるまでテストをかけばそれで十分、といった感じです。体系的なテスト手法やテスト技法で、ロジカルにテスト設計しなくても効果を出せます。
逆になれない技法で厳格なテスト設計を行おうとすると、スピードが落ちて、TDDの費用対効果の費用が悪化しがちです。
【発展的内容】もちろん はじめて学ぶソフトウェアのテスト技法 で挙げられているような、体系的なテスト手法やテスト技法を身につけて、有効なテストを軽快に確保できるようになると、TDDでも効果的なテストコードがかけるようになります。ただこれもTDDを回せるようになった次のステップで、逐次身につければよいと思います。
TDDの適用は一部だけでいい
あえていうまでもないかもしれませんが、TDDはコードの全てに適用できません。自動テストのテスタビリティを確保できる箇所のみで効果を発揮できます。テスタビリティを確保できない箇所でTDDを行うと失敗するか、費用対効果がマイナスになる可能性が高いです。
そのためTDDがやりにくいと感じたらTDDをやめても大丈夫です。プログラミングの2割ぐらいしかTDDができなくても、その2割で効果を出せれば御の字です。
【発展的内容】なおTDDの適用範囲を広げるためには、アーキテクチャレベルでの自動テストのテスタビリティの作り込みが必要です(ユニットレベルのテスタビリティはTDDで確保できます。それだけでは解決できない課題に対応するため、アーキテクチャレベルの工夫が必要になります)。これもTDDをまわせるようになった次のステップで挑戦するとよいと思います。
テストはあとから書いてもいい
テスト駆動・TDDとは呼べませんが、テストはあとから書いても大丈夫です。
というのも、TDD実践のとても重要な前提に、「自分の手足のように軽快にユニットテストを活用して、プログラミングを効率化する習慣の実現」があるためです。例えば、仕様に迷ったらテストファーストでテストをサッと書く、書いたコードに不安を感じたらテストをさっと継ぎ足す、のような状態です。
テストファーストであれ後付けであれ、とにかく気軽にユニットテストを活用する習慣を続ければ、その前提が満たされ、TDDを実践できるタイミングが一気に増えます。
なのであとからテストを書くのに気後れは不要です。気軽に・軽快にユニットテストをプログラミングに活用することが大事です。
ユニットへのテストでなくてもいい
パッケージや、アーキテクチャレベルのレイヤといった大きな粒度の方が、有効・堅牢な自動テストを書きやすい場面は少なくありません。そういう場合は、大きな粒度のテストでテスト駆動開発を行っても大丈夫です(いわゆる 実践テスト駆動開発 のOutside-In TDDやTDDのダブルループはその代表例です)。「TDDのテストはユニット(コードの最小単位)ごとに書かなければならない」という原理主義に拘る必要はなく、書きやすい粒度のテストで駆動させるのが費用対効果改善に有効です。
【発展的内容】これをやりやすくするためには、アーキテクチャレベルでのパッケージやレイヤ、バウンダリの粒度で、堅牢で自動テストを書きやすいインターフェースを確保するのが重要です。そこで確保したインターフェースに対する自動テストでテスト駆動を行うことで、TDDのテストの堅牢性、有効性を高められます。そうすればTDDのテストをCIなどで再利用できる場面が増えます。
TDDはチーム全体でやらなくてもいい
TDDは一人で実践しても費用対効果をプラスにできます。
プログラミングをしていれば、コードの仕様を検討したくなったり、不具合のリスクを感じたり、リファクタリングしたくなったりする場面に数多く遭遇すると思います。それらを個人レベルで気軽に解決するのがTDDです。
逆に個人では費用対効果がマイナスで、チーム全体でTDDしないと費用対効果を出せない、みたいな状況だと、TDDのやり方に問題がある可能性が高いです(これはチーム全体のスピード不足か、テスタビリティ実装スキル不足が原因であることが多いです。あるいは前述したゆるさを許さず完璧なTDD実践をしようとしているかもしれません)。
【発展的内容】ただテストをCIなどで継続的に利用するためには、チームでの推進が重要です。その実現には技術リードや開発インフラ整備の主導が求められます。これもTDDを個人で回せるようにしてから、次のステップで挑戦するとよいと思います。
余談:使い捨てを減らし、TDDのテストの費用対効果を高めるために
TDDは、プログラマが個人主観で気軽にテスト書いては捨てるアプローチでも効果が出せます。
ただし、次のステップとして、TDDで以下の実現を助ける(※)と、テストの使い捨ても減り、費用対効果をさらに高められます。
※TDDで上記を直接担保するのではなく、TDDのテストを流用して上記を助ける形です。
個人活動にフォーカスを絞った場合、この次のステップに進むためには次のスキルが求められます。
- 自動テストのテスタビリティを確保するスキル
- テストの保守性を作り込むスキル
- テスト対象の変更に強くする(変更性を高める)、テストコードの結合性・凝集性を改善する、テストコードの可読性を高めて保守しやすくする、といったものです。
- 有効なテスト設計を行うスキル。
- 必要なテストを見極め、そのテストの要求に対して、妥当なテスト設計を行うものです。リスクベースドテストや、有効・網羅的なテストを作るための各種技法の使いこなしが求められます。
このステップに進むためには、TDDの活動に、品質保証の戦略立て(TDDのテストを再利用してどのような責務を果たすべきかのテスト要求を明らかにする)、テストコードの改善・リファクタリング、テスト設計の改善、Outside-In TDDのような粒度の大きなテストサイクルの構築の活動の補強が求められます。
そしてテストの再利用を行うためには、チームレベルでの推進もある程度必要です。CIや共通のテスト自動化環境を整備し、チームでテストを継続的に改善・保守していく習慣が求められてきます。