TDDでのテストの構築と保守 その1 ユニットテストで支えるTDDの作業

書いた人
@goyoki

はじめに

こんばんは。goyokiです。

先日TDD Boot Camp for C++にてTDD入門の次のステップについて講演させていただきましたが、今回はその補足・復習として、ユニットテストの構築と保守について解説させて頂くことになりました。雑多な連載となるかもしれませんが、一連の記事がTDD習得の一助となれば幸いです。

テストコードの再利用

RED→GREEN→REFACTORの基本サイクルを回していくうちにありふれた作業となるものに「テストコードの再利用」があります。 例えば過去に書いたテストコードを使ってリファクタリングを実施する、過去に書いたテストコードを変更してREDにする、といった感じにです。

その「テストコードの再利用」は、製品コードの保守と同じく色々な手間や問題が存在します。ただ一方で、TDDの作業を様々な面で支えており、結果的にTDDの恩恵の一部を構成しています。今回は連載の前提知識として、そのユニットテストのサポート効果について解説したいと思います。

ユニットテストのサポート効果

コードの解析性の向上

Tests as Documentation

まずユニットテストは製品コードの理解を助けることで、TDDの各種作業をサポートします。

例えばテストコードの構造や名前の工夫により、実装仕様を示すドキュメントとしてユニットテストを扱えるようになります。このテストのドキュメントとしての用途はxUnit Test Patterns等でしばしば「Tests as Documentation」と呼称され、テストで達成すべき目標の1つとされています。

またコードの入出力をテストの追加でどんどん明示化していくことで、コードの理解を深めることができます。この手法は仕様化テスト(Characterization Test)と呼ばれるテクニックです。中でもコードのふるまいを学ぶことを目的とした仕様化テストは学習テスト(Learning Test)と呼称されています。やはりこちらもテストを一種のドキュメントとして扱います。

Tests as DocumentationによるTDDのサポート

こうしたユニットテストのドキュメントとしての有用性は、リファクタリング・Assertファースト両方の作業をサポートします。

例えばテストコード上で製品コードの用例・使われ方を表現してみることで、製品コードのインターフェースの良否を評価することができ、リファクタリングの必要性を決定することができます。

 

また、以下のような問題の絞り込み手段を実現することから、Assert Firstや意図しないデグレード対応のサポートとしても活用できます。

  • クラスやメソッド単位で実行コードの絞り込みや切り分けができる。
  • 実行に時間がかかるコード、ブラックボックスなコンポーネント、特殊な実行環境といった、解析の障害を排除できる。
  • MockオブジェクトやStub等の活用で、End-to-Endでは実現が困難な入力や初期状態を実現できる。

この工夫の具体例にデバッギングテストが挙げられます。デバッギングテストはバグの再現手段・バグ発生個所の切り分け手段として、ユニットテストを活用する手法です。具体的には以下のような手順を取ります。

  1. ユニットテスト上でバグを再現する
  2. テストの追加でバグの発生個所を特定する
  3. バグを修正し、テスト結果の変化を見て修正が達成されたか確認する

 

なおこのテストコードで実現するドキュメントは、(テストファースト手法を除けば)従来のjavadocやdoxygenなどで実現されるような、コードの解説資料のようなものに位置付けられます。ただテストコードの場合、従来の資料と違ってテスト結果でドキュメントとコードの矛盾を把握可能なため、追加・変更が柔軟にできるのが強みです。

コードの再利用性の向上によるTDDのサポート

さらにユニットテストはコードの再利用性を補強する点で、リファクタリングおよびAssertファーストによる追加変更を支えていきます。

例えば定番ですが、リファクタリングでは、事前にユニットテストで既存コードのふるまいを保護することで、コードの設計改善をデグレードなく行えるようにしていきます。

また追加・変更でも、以下のような手順を取ることで、デグレードを防ぎ、かつ変更・追加が意図したとおりに達成されたことを確認できるようになります。

  1. 変更したくないふるまいをユニットテストで保護する
  2. 変更後のふるまいに対するユニットテストを追加して、テストを失敗させる
  3. コードを変更して、失敗していたユニットテストをパスさせる。

なおこの「テストで保護してからコードに手をつけるアプローチは、Cover & Modify(保護して変更する)と呼ばれます。Cover & Modifyのメリットは小さくなく、それを実施できるかどうかは、変更・再利用を安全かつ簡単にできるかどうかに大きく影響します。すなわちCover & ModifyのやりやすさはTDDのやりやすさに直結するといっても過言ではありません。

TDDのやりやすさの維持

そしてユニットテストは自動化された回帰テストとして継続実行することによって、テストが損なわれる変更の早期検出や、コードカバレッジといったテストの継続評価が実現されます。 そうした継続的な保守サポートは、テストをいつでも利用可能な状態に維持することを助け、前述した「仕様化テストでコードの理解を進める」「Cover & Modifyで変更の安全性を高める」といったメリットをいつでも活かせられるようにします。

ユニットテストの再利用について

上記のような効果を持つため、ユニットテストの再利用・継続的活用はTDDにおいて推進すべきものと考えています。ただユニットテストの再利用を続けるためには、様々な課題に直面することになります。

例えばその1つとして、テストの網羅性の作りこみがあります。例えばTDDでも網羅性の劣ったテストを書くと、REFACTORでバグを埋め込んだり、Assertファーストで適切にコードを追加できなくなってしまったりするリスクが高まります。しかしかといって、時間のかかるテスト設計手法の採用や過剰なテストの網羅性作りこみを行うと、保守コストやTDDの効率を悪化させる恐れがあります。

次回以降では、そうした課題がどういうものか、またそれにどう立ち向かっていくかについて解説していきたいと思います。

Last modified:2011/11/30 22:41:44
Keyword(s):
References:[xUTP Magazine 0002号] [ぺけま]