川村渇真の「知性の泉」

テスト:システムの信頼性の向上を支援する


テストとデバッグは違うもの

 システムの信頼性を確保するためには、信頼性を高めるような設計だけでは不十分で、きちんとしたテストの実施も必須である。現状の開発では、非常に残念なことだが、テストの概念すら理解していないのではと思うような場面が数多く見受けられる。その意味で、まず最初にテストの意味や役割を解説しよう。
 まず最初に理解すべきなのは、テストとデバッグの違いだ。デバッグとは、発見されたバグを消すための、プログラムやシステムの修正を指す。デバッグのためには、バグを発見しなければならない。その作業こそテストである。テストによって発見されたバグを、デバッグで直すという関係だ。デバッグは、該当部分を開発した人が行う。しかし、テストだけは、開発者本人が行ってはいけない。テストが甘くなって、システムの品質が低下しかねないからだ。
 バグという言葉も、正確に理解して用いる必要がある。バグとは、仕様どおりに動作しない現象を指す。機能が不十分とか使い勝手が悪いというのは、仕様の問題であってバグではない。「仕様が悪い」とか「仕様の欠点」と表現する(余談だが、悪い仕様のまま製作工程へ進むのは、きちんとしたレビューを実施せずに仕様を決めていると起こりやすい)。テストというのは、バグの発見が目的なので、仕様どおりに動作しない箇所を発見する作業である。当然、仕様書も書かないプログラムやシステムは、どのような動作が正しいのか判断できず、まともなテストなど不可能だ。というわけなので、仕様書ができあがったら、テスト内容の設計が開始できる。

テストを可能な限り自動化する

 テストで大切なのは、“より多くのバグ”を“最小限の手間”で発見することだ。テストに割り当てられる人員や時間は限られているので、効率を重視しなければならない。もう1つ、大切なことがある。発見したバグは、再現可能な形で提示することが求められる。「〜のような現象が発生した」だけではダメで、「〜の条件のときに、〜の現象が発生した」と報告できなければ、デバッグする人が大変だからだ。
 効率と再現性を重視するなら、テスト内容をきちんと設計する必要がある。システムやプログラムの仕様書をもとに、テスト条件やテスト項目を洗い出す。それに合わせて、テスト環境やテスト方法を細かく設計する。
 実際のテストでは、テスト項目ごとに個別のテストを実施する。その際、できるだけ自動化することが大切だ。自動化したテスト手順は、すべてのテスト項目で以下のようになる。

1、環境の準備:テスト条件を整えるためにデータなどをロード
2、テスト実施:指定されたテスト処理を実施する
3、結果の記録:テスト結果を調べて記録する(正常でもバグでも)
4、環境の掃除:テスト用データなどを削除し、元の状態に戻す

最初の作業では、テスト条件を満たした環境やデータを用意する。データベースにデータをロードするとか、加工用のファイルを規定のディレクトリにコピーするとか、決められた準備を行う。次に、対象となるソフトを動かし、その結果が正しいかを調べる。正常でもバグでも、結果をファイルとして保存する。最後に、使用したファイルなどを削除して、1つ分のテスト項目が終了する。結果が正しいかどうかの確認は、正常に処理されたときのファイルや値を用意し、それと比較する方法で調べる。いくつかのテスト項目を連続して実行し、それぞれに記録された結果を見てバグの有無を判断する。
 これらの作業は、全体がバッチ処理ならば自動化は簡単だ。また、対話型のソフトでも、マクロなどでバッチ処理で操作できるなら、自動化が可能になる。できるだけ使ったほうがよい。ただし、画面表示が正しいかを確認する部分だけは、自動化が難しい(不可能ではないが非常に大変)。バッチで動かしている過程を人間が目で見て、バグを発見するのが現実的だ。
 以上のような自動化を実現するには、簡単なテストシステムを用意し、テストデータも作る必要がある。その手間が負担だと感じるかもしれない。しかし、きちんとしたテストでは、テスト項目の数がかなり多いので、自動化したほうが格段に効率的となる。

テスト内容は何段階かに分割して実施する

 開発の規模が大きいと、多くのプログラムが作られ、それらを結合してシステムができあがる。テストの作業も、開発の流れに合わせて実施しなければならない。たとえば、次のような内容が考えられる。

1、単体テスト:個々のプログラムを単体でテスト
2、サブシステムテスト:プログラムを結合したサブシステム単位でテスト
3、システムテスト:すべてのサブシステムを結合した状態でテスト
4、運用テスト:実際に運用される状態でテスト

このように、個々のプログラムから最後の結合状態まで、順番にテストを実施する。プログラム単体を先にテストすることで、結合したときにバグが多くてテストにならないといった、困った状況を防げる。
 プログラム単体でもシステム全体でも、テスト項目を決める際には指針が必要だ。指針を設けることで、テスト項目の漏れを減らしやすい。どのような指針を採用するかは、対象となるシステムによって少し異なる。1つの例として、以下のような指針がある。

1、正常基本:エラーでない基本機能を動かす
2、正常限界:エラーにならない限界値で機能を試す
3、エラー:エラーとしてはじく機能を試す
4、環境限界:環境の限界状態での性能や動作を調べる

細かなテスト項目は、このような指針に合わせて設計する。「正常基本」では、すべての機能を対象に、一般的な値や使い方を条件とする。続く「正常限界」では、各機能の仕様上の限界値を条件に設定する。たとえば、ユーザーの設定を10件まで登録できる仕様なら、10件の登録が正常にできるかをテストする。次の「エラー」は、エラーが出る条件を試し、本当にエラーではじくかどうかを確かめる。最後の「環境限界」は、メモリーの余裕が少ないとか、環境が限界の状態での動作をテストする。
 かなり簡単にだが、テスト項目の作り方をまとめてみた。このような考え方でテスト内容を設計し、それに沿ってテストを実施する。実際にテストする段階では、決められたとおりに機械的に処理を進めるだけである。そのため、テスト内容の善し悪しでテストの質は決まる。テスト内容を適切に設計すれば、テストによってかなりのバグを発見でき、システムの信頼性を向上させられる。

テスト工程は開発がある限り必要

 作成したテスト内容は、開発者にも公開する。テストが終了した時点ではなく、テスト仕様としてまとめた段階でだ。こうすると、開発者が考慮していなかった条件を発見して、早目に直すことができる。また、致命的な欠点を発見することもある。できるだけ早く開発者に見せることが大切だ。
 テストの工程は、開発ツールが進歩することで不要になると思ってはいないだろうか。残念ながら、開発が続く限り永久に必要だ。その理由を簡単に説明しよう。ツールが高度に進歩したとしても、処理内容だけは定義しなければならない。たとえば、計算式を書くだけで対象データを全部処理するとかだ。その場合、定義の部分だけが開発対象となる。では、定義が正しいかどうか、テストなしで判断できるだろうか。世の中の全部の知識を参照でき、それと比較して定義を検査する方法が使えたとしても、一致しない定義をエラーとは判定できない。新しい式を作って試したいこともあるからだ。複雑なシステムでは定義の数も多く、どこかをタイプミスするかも知れない。たとえば、既存知識内で係数の値が50の式を、40の値に変えて定義してあったとする。これが新しい式なのかタイプミスなのか、自動的に調べることは不可能だ。また、システムが複雑になると、個々の定義だけを調べるよりも、システム全体としてテストしたほうがよい。使い勝手なども含めて、設計上のミスまで発見できるからだ。というわけで、大きなシステムでは本格的なテストが必要となる。
 現実の世界では、オブジェクト指向の考え方を取り入れたツールが数多く登場している。このようなツールを使って開発すると、製作の工程では効率が上がるものの、分析やテストの工程はあまり変わらない。ただし、バグが出にくくなることで、テストの実施回数だけは減る可能性がある。しかし、テスト内容の設計やデータの準備などの作業量は同じなので、テスト全体の工数が大きく減ることはない。
 以上のように、上手なテスト方法をマスターすることは、開発ツールの進歩に関係なく役立つ。システムの信頼性の向上を助ける技術だけに、開発者にとって価値が大きい。

(1997年12月23日)


下の飾り