新人技術者のためのロジカル・シンキング入門(7) ―― 「ひたすら流すだけのテスト」にさよなら
【3】意外と難しい「単体テスト」,「結合テスト」の区別(1/2)
次に,「単体テスト」と「結合テスト」という分類の仕方について考えてみることにしましょう.冒頭の例でいうと,Gさんが担当しているモジュールの単体テストを終えたら,次の工程で全体の結合テストを実施することになります.単体テストから結合テストへという進め方は,一つ一つ部品を確実に組み立てる立場からすれば当たり前のことのように思えます.しかし,実際の開発ではこの「単体」と「結合」の区別はそれほど容易ではありません.
開発者の中には,「単体テストとは関数一つ一つに対して行うもの.それらの確認が終わって初めて結合テストができる」とやや教条主義的に考えている人もいます.また,結合テストはビッグバン・テスト(単体が終わったら全体を一気につなげる)以外考えず,段階的に結合していくことにはあまり関心のない人もいます.このような考え方が行き過ぎると,いろいろな悲劇や喜劇を生む原因にもなります.
● 単体テストは関数単位とは限らない
いわゆるオブジェクト指向を採用したシステムでは,リスト1のような小さな関数にお目にかかることがよくあります.リスト1は,例えば処理フレームの大きさを知るためにこのようなインターフェースを用意したのだと考えられます.
リスト1 単体テストの実施が無意味な関数の例
見た目で内容が確認できるような小さな関数にも単体テストを実施するのはナンセンス.バグの原因として怖いのは,いかなる場合も正しいタイミングで必ず使われているかどうかであり,そのためのテスト項目がむしろ必要.
このように関数化しておくと,復帰値が単なるdefine文による固定値ではなく,将来の仕様変更で何らかの計算を行うように変わったとしても,ほかの部分のソース・コードはこの関数を呼ぶ形のままで変わることはありません.いわゆる「カプセル化」と呼ばれる考え方です.
このような小さな関数の場合,関数単体の動作を確認するという行為はナンセンスです.なぜなら,関数が仕様通り作られているかいないかは,ソース・コードを見れば十分把握できるからです.小さな関数が多く集まったシステムの場合,バグの原因となるのはむしろ関数のコール・シーケンス,すなわち呼ばれるべきタイミングで呼ばれるかどうかということにつきます.リスト1のような関数の場合,この関数が呼ばれずにバッファ・サイズが不定値のままアロケートされてしまうようなことが例として挙げられます.こうしたバグは,関数単体をいくらテストしてもチェックすることはできません.
テスト項目とはすなわち,このようなバグを効果的に発見できるケースの組み合わせにほかならないのです.
● 単体テストは機能ブロック単位で
連載第3回でも解説しましたが,筆者は設計の単位は機能ブロックで分けていくべきだと考えています(図6).ですから当然,「単体テスト」と呼ばれるテスト・ケースも,この機能ブロックが単位となります.この機能ブロックごとに正常系,異常系を含めてあらゆる動作パターンを検証し,バグがないかどうかを確認するようなテスト・ケースを組み立てることが重要となるのです.
図6 単体テストは関数ではなく機能ブロックに対して行う
関数単位ではなく,機能ごとに動作確認を行うのが単体テスト.機能間のインターフェースを中心にブロックをつなげたときの動作確認を行うのが結合テスト.こう考えれば,単体テストと結合テストを併用することでバグを効果的に取り除ける.「単体=関数」という考え方にとらわれるとうまくいかない.
例えば,リスト1に示した不定値によるメモリ・アロケートの例で考えてみます.このようなバグは,想定しなかった例外的なパターンで生じることが少なくありません.従って,機能ブロックの異常系のテスト項目を充実させてバグの混入を防ぐ,という対策を採ることができるでしょう.正常な動作のケースでは現れにくいバグだからです.
もちろん,あまりに想定外のパターンが生じることがないように作っておくことは,設計の段階から重要となります.異常ケースについてすべてのパターンを考えることはなかなか難しく,また時間的な制約も無視できないからです.