図3●制御パスをつかむ<BR>ifなどの条件分岐に着目してプログラムのロジックを抽象化した「フローグラフ」の例。プログラムの開始点から終了点までの経路を「制御パス」と呼ぶ。抽象化すると,制御パスを考えやすくなる
図3●制御パスをつかむ<BR>ifなどの条件分岐に着目してプログラムのロジックを抽象化した「フローグラフ」の例。プログラムの開始点から終了点までの経路を「制御パス」と呼ぶ。抽象化すると,制御パスを考えやすくなる
[画像のクリックで拡大表示]
図4●制御パス・テストの3つの基準&lt;BR&gt;選択する制御パスの要素により,(1)すべての命令を1回は実行する「命令網羅」,(2)すべての条件分岐を1回は実行する「条件網羅」,(3)すべての制御パスを実行する「経路網羅」――の3種類がある。命令網羅は条件網羅に含まれ,条件網羅は経路網羅に含まれる。最上位の経路網羅を実施することが理想だが,コストや期間などを考慮すると,条件網羅レベルをクリアするのが現実的である
図4●制御パス・テストの3つの基準<BR>選択する制御パスの要素により,(1)すべての命令を1回は実行する「命令網羅」,(2)すべての条件分岐を1回は実行する「条件網羅」,(3)すべての制御パスを実行する「経路網羅」――の3種類がある。命令網羅は条件網羅に含まれ,条件網羅は経路網羅に含まれる。最上位の経路網羅を実施することが理想だが,コストや期間などを考慮すると,条件網羅レベルをクリアするのが現実的である
[画像のクリックで拡大表示]
表2●ホワイトボックス・テストを支援する主なツール
表2●ホワイトボックス・テストを支援する主なツール
[画像のクリックで拡大表示]
図5●静的解析の効果は高い&lt;BR&gt;静的解析とは,プログラムを実行せずにソースコードの内容をチェックする作業。バグを生みやすいコーディングはしていないか,可読性や保守性が下がるコーディングはしていないか,などの観点から解析する。コーディング終了時にレビューとして実施することも多い。レビュアのスキルが高ければ,メモリー・リークやマルチ・スレッドのバグも発見できるなど,より効果が高まる
図5●静的解析の効果は高い<BR>静的解析とは,プログラムを実行せずにソースコードの内容をチェックする作業。バグを生みやすいコーディングはしていないか,可読性や保守性が下がるコーディングはしていないか,などの観点から解析する。コーディング終了時にレビューとして実施することも多い。レビュアのスキルが高ければ,メモリー・リークやマルチ・スレッドのバグも発見できるなど,より効果が高まる
[画像のクリックで拡大表示]

 テストの最初に位置する「単体テスト」(モジュール・テスト,ユニット・テスト)は,すべてのシステムで実施されるべき基本的なテストである。実装された関数やメソッド(以下,プログラム)の内部構造のバグを取る。通常,コンパイルした直後に実施され,デバッガなどを用いるケースもあるため,プログラマ自身が実施することも多い*2。後工程になるほどバグの修復コストが高くなることを肝に銘じて取り組みたい。単体テストで実施するテストには,以下の3つがある。

(1):ホワイトボックス・テスト

 ホワイトボックス・テスト*3では,テスト対象の内部構造を抽象化し,これに基づいて「実装した関数やメソッドの論理が正しく動作するか」を確認する。図3[拡大表示]に示したフローグラフなどを用いれば,内部構造が把握しやすくなる。具体的なテスト手法としては,下記に示す「制御パス・テスト」や,「再帰テスト*4」が挙げられる。

● 制御パス・テスト

 プログラムの論理を抽象化した上で,入出力に着目し,様々な入出力に対して意図した経路を通過するか,意図した結果が出力されるかを確認する。全経路に対して,どこまでテストするかを示す「網羅基準」によって,3段階に分類される(図4[拡大表示])。(1)プログラム上のすべての命令を最低1回はテストする「命令網羅」,(2)プログラム上のすべての条件分岐を最低1回はテストする「条件網羅」,(3)プログラム上のすべての経路を最低1回はテストする「経路網羅」——である。

 コンパイルした単位でプログラムをテストするのであれば,経路網羅テストが可能だ。しかし,参照関係や呼び出し先まで含めた範囲を単体テストの対象としてとらえた場合,経路の数は膨大になる。コストと時間を効率よく利用するために,(1)プログラム分割などでテスト対象はできるだけ小さくする,(2)テスト・ケースの作成は条件網羅レベルをクリアする,ことが大切だ。

 また,自動的に経路や条件を調査してテスト・データを生成し,膨大な経路網羅テストを実施してくれるツールの利用も有効である(表2[拡大表示])。

(2):クラス構造テスト

 オブジェクト指向開発では,関数やメソッドは特定クラスの一部として様々なクラスから参照される。ただし,個々のプログラムにバグがない場合でも,クラス間の継承関係*5や結合度*6,凝集度*7などが最適化されていない場合,無関係なはずの修正や追加が広範囲に影響を及ぼしたり,思わぬ不具合を発生させることがある。

 そのため,プログラムのソースコードを解析して,結合度や凝集度についての問題を確認する「クラス構造テスト」を実施する。

 代表的な手法には,クラス間の結合が強過ぎないかどうかを確認するための「クラス結合度テスト」が挙げられる。主に結合度を左右する「受け渡しデータ」に着目し,そのデータ構造や内容から,高い結合度を持つプログラムを洗い出す。実施方法は,テスト・データを使わずに,1行1行ソースコードを読み解いていく「静的解析テスト」となる*8。ただし,かなりの時間と根気が必要になるため,先述したツールに解析させることも検討したい。

 さらに,ツールによっては,コード・インスペクション*9を実施する機能を備えている。これらの機能を活用すれば,結合度だけではなく,プログラムのバグが入り込みやすいポイントやコーディング・ミスも発見できる(図5[拡大表示])。オブジェクト設計時のミスや不備も検出できる。

 ただし本来,クラスの継承関係などは,設計段階にクラス図などで十分に検討され,実装時にはコード・インスペクションやレビューを通して検査されるべきだ。短期開発が進む昨今では,そこまで詳細に設計レビューやコード・インスペクションが実施されることは少なくなっている。しかし,自分や他人のソースコードを読む力がないと,いざトラブルという時に役に立たない開発者になってしまう恐れもある。ツールをただ利用するだけでなく,解析観点を学んでほしい。

(3):回帰テスト

 ソースコードを修正/追加した場合,修正した部分以外に影響が及んでいないこと,修正により予期せぬバグを作り込んでいないことを,再テストで確認する手法が「回帰テスト」だ。

 カプセル化やコンポーネント化などを駆使して再利用性を高めているシステムでは,修正したモジュールが多くのプログラムに関係している可能性があるため,回帰テストは特に重要だ。また,クラス間の結合度が高い場合も,回帰テストの必要性は高い。

 ただし,修正の影響範囲を手作業で特定することは時間がかかる。そのため,機械的に呼び出し関係を洗い出してくれるツールの導入が有効になる。ツールを使い,回帰テストが必要なプログラムを検出し,修正前と修正後のプログラムを同一のテスト・データでテストすることで,想定外のバグを防ぐことができる。

 単体テストで最近注目されているのが,「XP(Extreme Programming)*10」で提唱されている「テスト・ファースト」というアプローチだ。

 テスト・ファーストでは,(1)まず,プログラム本体のコードを実装する前に,そのプログラムが満たすべき条件をテスト・コードとして記述する,(2)次にそのテスト・コードを満たす,必要最低限のコードをプログラムに実装する。そのため,実装コードは常にテスト済みのコードである。また,テスト・コードを追加していきながら実装コードを増やしていくため,回帰テストも同時に実施できる。また,XPにおけるテスト・ファーストでは,「JUnit」(http://www.junit.org/)など,テスティング・フレームワーク*11の利用が前提になっている。

 テスト設計書やテスト・ケースを先に作ることは,先述した通り,テストにおいては基本的なことだ。テスト・ファーストは,プログラムの作成方法を,コード実装優先からテスト優先に切り替えたことが特徴。ただし,テスト・ファーストにおいても,ホワイトボックス・テストや後述のブラックボックス・テストの観点でテスト・コードを記述することが大切だ*12


倉田 克徳
エス・キュー・シー 代表取締役
石川 和則
エス・キュー・シー 取締役 事業企画室長