読者です 読者をやめる 読者になる 読者になる

TDDとテスト容易性(試験性)の関係

 最近ソフトウェアテスト方面の方々に対してTDDの講義を行う機会を頂いていて、テスト・QCの視点とTDDの視点の関わりについていくつかの点で考えるようになっている。その延長線上で、少し前に話題になっていた、TDDとテスト容易性(試験性)の関係についても考えを整理しているのだけれど、区切りのいいタイミングなので今回文章としてまとめてみたい。
 なお今回は、割と代表的な単体テストの運用形態と思われる、ウォーターフォール的に単体テストを用意するアプローチ(設計のブレークダウンで確保するアプローチ)と、TDDで0から単体テストを蓄積していくアプローチ(インサイドアウトのTDDで確保するアプローチ)を比較していく形で、TDDとテスト容易性の関係について説明していきたいと思う。

無防備なテストラストの弊害

 まず前提の話になるけど、取りあえず何も考えずにテストラスト(後からテストを書くアプローチ)で単体テストを書くのは困難だと一般的に言われる。
 理由としては、実行環境や自動化の制約(DBやH.W.といった本番環境のリソースを操作してしまう、実時間に依存する、GUIに依存する、実行に費用がかかる等々)がプログラミング作業の中で溢れていて、注意しないとそれらが単体テストの障害としてコードに容易に入り込んでくる背景による。

 そのため単体テストを実施するためには、それなりに工夫をして単体テストのテスト容易性(以下単体テスト容易性)を確保する必要性が出てくる。その手法としてはいくつかあるが、今回は冒頭でも触れた「設計のブレークダウンで確保するアプローチ」と「インサイドアウトのTDDで確保するアプローチ」を挙げたいと思う。

設計のブレークダウンで確保するアプローチ

 Vモデルのような直列のプロセスでは、上流工程から単体テスト容易性が確保されるように配慮しながら設計を進めるアプローチをとることがある。具体的には、以下のようなプロセスで、実装成果の単体テスト容易性を保証させる場合がある。

  1. 計画段階や要件定義の段階で要件に単体テスト容易性を加える。
  2. きちんとした仕様分析によって仕様のトレーサビリティを維持しながら、システム設計、構造設計、コンポーネント設計、単体設計と仕様をブレークダウンしていく。

 このプロセスでは、単体テスト容易性の要件を先に定義して、それに設計をあわせていくことで、必要な単体テスト容易性を確保する。またここで単体テスト容易性の障害は、要件未達を発生させるリスクとして設計の過程で排除されていく。

インサイドアウトのTDDで確保するアプローチ

 一方、0から単体テストを蓄積していくインサイドアウトのTDDでは、上記のようなトップダウンではなく、テストファーストによるボトムアップの方法で単体テスト容易性を確保していく。
 具体的には、最初にテストを書いて、次にそれに合わせて製品コードを実装する手順を踏むことで、半ば強制的に実装をテストコードに対して最適化させる。そこでは単体テストにとっての障害がDIやAOP、Mockといった技法で自ずと回避されるほか、non-virtualやカプセル化といった設計構造もテストコードを阻害しないような形をとるようになるため、大抵自ずと実装コードが単体テスト容易性を備えるようになる。

実現される単体テスト容易性の違い

 そこで本題だけど、上記の2つのアプローチを扱う上で留意すべきこととして、それぞれで実現される単体テスト容易性が同じものとは限らないことが挙げられると思う。

 というもの、まず設計のブレークダウンで確保する単体テスト容易性は、テストが要件や上流設計の対をなすため、仕様ベースのテスト設計技法や、仕様保証(設計保証、要件保証)に適した性質を持つようになる。それに対して、インサイドアウトのTDDで確保する単体テスト容易性は、実装上の細かな要求(細かなフィードバック、リファクタリング)や、開発者のテストリテラシーに沿ったものになる。

 もちろん両者を内包した単体テスト容易性を目指すことも可能だが、それは意図的に目指さないと実現を保証できない点に留意が必要だと思う。例えばインサイドアウトのTDDでは、テストコードと上流仕様とのトレーサビリティを保証するプロセスが通常考慮されない。そのためTDDで設計のブレークダウンで得るテスト容易性を確保するためには、上流仕様に合わせてテストコードを再構築したり、上流仕様に沿うようにTDDを進めたりするプロセスが必要な場合が多い。
 なおさらに両者を完全に一致させることももしかしたら可能だろうが、設計・要件保証のためにテストを書くという基準と、TDDでの実装のためにテストを書くという基準は、無理に一致させようとすると大抵非効率になるので、あまり現実的でないと思う。

テスト容易性(試験性)の保証の扱い

 一方、単体テスト容易性と同様にテスト容易性(試験性)に関しても注意が必要だと感じる。
 例えばISOの品質特性で規定される試験性をコードに要求する場合でも、アプローチの差異から以下のような違いが出てくると思う。

  • 設計のブレークダウンで確保:自然な流れでできる可能性が高い。例えば品質要件にISOの試験性を加えて仕様をブレークダウンすることによって、実装レベルまでその要件を反映させることができる場合がある。
  • インサイドアウトのTDD:プロセスによる補助が必要な可能性が高い。例えば試験性の品質要件に従って別途テストを設計しておき、TDDでそれを内包するようにテストコードを作っていくようなプロセスが必要かもしれない。

それぞれの補完

 ただ当然として、上記のアプローチは対立するものでなく、一緒に実施することも可能だろう。
 例えば単体仕様レベルまで仕様をブレークダウンするプロセスは、ここでも書いたようにとても重く、規模や業態によっては実施が難しくなっている。そうした困難さをTDDによる担保で回避することは十分可能だろうう。また設計のブレークダウンで確保されるテスト仕様等は、奔放な状態になることもあるTDDのテストに適切な規律を与えられる場合がある。
 ともかく両者ともうまくやれば品質を向上させる効果を持っているので、両方できるのなら総合的な視点でうまく相乗効果を目指していくのが理想的だと感じる。