作成日 2017/4/2
最終更新日 2017/4/9
テストの品質
ここではテストの品質について説明します。
テストには良いテストと悪いテストがあります。それについて説明します。
また、どうすればテストの品質を上げられるかについても可能な限り説明しようと思います。
良いテストとは何だろうか?
まずは、書籍「基礎から学ぶソフトウェアテスト 〜テストの「プロ」を目指す人のために〜」(ISBN4-8222-8113-2)のp122から
優れたテストケースは以下の基準を満たしている。
- 不具合を検出できる可能性が高いこと
- 重複がないこと
- 最善であること
- 単純すぎず、複雑すぎないこと
- 検出の方法が明示してあること
説明します。
■不具合を検出できる可能性が高いこと
説明必要だろうか?テストは不具合を検出し修正するために行うので、検出できる可能性が高いテストの方が良いに決まっていますね。
■重複がないこと
これも説明が必要だろうか?同じことをやっても意味ないからね。重複など無い方が良いに決まっています。
■最善であること
不具合を検出できる可能性が高いテストを行えってことのようです。
■単純すぎず、複雑すぎないこと
書籍には、複数のテストを組み合わせて、1つにすれば、テスト時間が減るよって書いてあるが、やりすぎは危険だよって言っている。
■検出の方法が明示してあること
テスト仕様書では、入力(手順など)と期待値(予想結果)を記載しますが、これをちゃんと書けってことです。まあ、無かったら不具合かどうか分からないから当然だが。
以下、だるまが考えている優れたテストの基準です。
基本的な考え方は「費用対効果」です。
「品質確保の難しさ」の「1.そもそも全パターン試験できない」で説明した通り、全てのパターンのテストを実施することはできません。その中でどうやって、不具合を発見し、減らすか?です。
- 1回のテストにかかる時間が短い
- テストが自動化されている(テストの実施を人の手で行うのではなく、テストプログラムやツールが行う)
- テストプログラムが分かりやすく、修正しやすい
- 不具合の原因を調査するために何度もテストを実行しない
- 重複が無い
- 不具合を検出できる可能性が高い
- 手を抜けるところは手を抜く
説明します。
■1.1回のテストにかかる時間が短い
これはかなり重要だと思ってます。例えば、1回のテストに10分かかるのと、1分かかるのでは、後者の方が10倍できる。
1回のテストに時間がかかると、修正後の再試験にコストがかかることになります。
そうすると、使用者が意識しない内部的な品質不良(例えば、保守性の不良)の発見がされても、もういいやってことで、修正されないということがありました。
例外として、意図的に長時間実行を行うテスト(長時間連続実行しても、メモリリーク、メモリやディスクの断片化による速度低下、ディスク不足などの問題が起きないことを確認する)については除きますが、この手のテストの実施は最低限にした方が良いです。
すなわち、これ以外のテストやレビューなどによって不具合を見つけるなり予防しておかないと駄目ってことです。
メモリリークは、長時間実行を行うテストで無くても発見可能です。断片化、ディスク不足は設計段階でチェックしよう。
■2.テストが自動化されている(テストの実施を人の手で行うのではなく、テストプログラムやツールが行う)
「1回のテストにかかる時間が短い」の具体策です。
手で行うのと、自動化され、プログラムが行うのとでは「1回のテストに10分かかるのと、1分かかる」(10倍の差)レベルではなく「1分と0.01秒」と言う感じで、1000倍以上(1分と0.01秒なら6000倍だが)の差がつきます。
テストが自動化されていると、不具合修正後の再確認にコスト(あるいは時間)がかかりません。ただし、問題なのはテストの自動化に(テストプログラムの作成に)コストがかかることです。
あ、ここまで説明したら、分かると思うけど、テスト中のデバッグ実行は、効率の面から最後の手段です(デバッグ実行は効率が悪いため)。
■3.テストプログラムが分かりやすく、修正しやすい
「テストが自動化されている」の補足事項的なもの。
不具合修正したときにテストプログラムも直します。このとき、テストプログラムが難解だと、コストがかかってしまいます。
■4.不具合の原因を調査するために何度もテストを実行しない
これも、「1回のテストにかかる時間が短い」の具体策です。
テストプログラムの途中途中で、不具合の原因解明につながる様な情報をログを出力しましょう。
■5.重複が無い
考え方は、書籍と一緒です。
■6.不具合を検出できる可能性が高い
考え方は、書籍と一緒です。不具合が発生しそうな場所は決まっています。そこを確実に行う。
ある程度、テストしたら、テストの観点を変える必要があります。例えば、ずっと、命令網羅・分岐網羅でテストを行うのではなく、機能漏れがないか?、境界値、性能、ユーザーインタフェースをといった他の観点でテストを行うべきです。
■7.手を抜けるところは手を抜く
手を抜く(手抜き)というと、ものすごく悪いイメージがありますね。
でも、ツールが作成したソースコード(例えば、Eclipseにはgetter,setterを自動生成してくれる機能がある)に不具合がある可能性は非常に低いのですから、そこを頑張ったところで意味が無い可能性が非常に高いです。
設計時、プログラミング時、テスト時に考慮することを説明したいと思います。
重要なことを、まず、言います。
もう、言っているようなものですが、テストの品質を上げようと思ったら、設計段階から考えないと駄目です。
JIS X 0129-1で定義される品質の「保守性」の中の「試験性」はテスト工程でのみ決まるわけではありません。設計段階から考える必要があります。
では、思いつくものを説明します。
繰り返しになりますが、下記は「保守性」の中の「試験性」についてのみの考慮です(2017/4/9追記。「試験性」も含まれています。ただし、これも試験を効率化するためのものです)。当然ですが、実際には他の品質への考慮も必要ですよ。
- [設計時]ライブラリがあるときは極力使う設計にする(自作しない)
- [設計時]モジュールの強度(凝集度)を上げ、結合度を下げる設計にする
- [設計時]モジュールの相互参照をやらないようにする
- [設計時]変更を受けにくい箇所と受けやすい箇所で、モジュールを分ける
- [設計時]テストの自動化が行いやすいところ、行いにくいところでモジュールを分ける
- [設計時]再現性が低くなるような箇所(他のプロセス、スレッド、DBのselectなど)に注意して設計する
- [設計時]ログ出力が遅いと、かなりまずいので、速度を考える
- [設計時]ログの出力項目にも注意を払う
- [設計時]プログラムが強制終了したときのことも考えよう
- [プログラミング時]再現性が低くなるような不具合(メモリリーク、メモリ破壊など)に注意してプログラミングする
- [プログラミング時]理由が無い限り、1つの関数の行数が大きくならないようにする
- [プログラミング時]エラー処理をちゃんと行って、エラー発生時にちゃんとログを出力する
- [プログラミング時]InfoログやDebugログもちゃんと出力する
- [プログラミング時]1つ行で何個も関数コールしない
- [テスト時]テスト用のツールを勉強する。特にスタブ用のツール。
- [テスト時]複数のテストを一度に行うテストプログラムの作成はほどほどに
- [テスト時]テストドライバやスタブの中でもログ出力する
説明します。
■1.[設計時]ライブラリがあるときは極力使う設計にする(自作しない)
え?だって、すでにあるものを使えばテストしなくていいじゃん。
少なくとも使おうとするライブラリが、標準のライブラリ(javaの標準ライブラリ、C++の標準テンプレートライブラリ、POSIXライブラリなど)や購入したライブラリ、広く使用されているライブラリ(Apacheライブラリ)だったら、おおよそ大丈夫だ。
社内で作ったライブラリ、、、はそれ、本当にテストしたよな?だるまは、他の会社が作った出来の悪いライブラリのおかげで痛い目にあったことが1度ではないです(念のため:0回と言う意味ではない)。
潜在バグがあったり、動くけど速度が遅かったりとかしないよね?まじで。プロジェクト炎上の原因になったことがあるので勘弁して欲しいです。
あと、組込みの場合、広く使用されているライブラリが使用できない場合が多いです。例えば、ApacheライブラリはJ2SEでは使えるでしょうが、J2MEでは使えないだろう。その場合は諦めて自作するしかないな。
■2.[設計時]モジュールの強度(凝集度)を上げ、結合度を下げる設計にする
結合度は、あまり説明しなくても、分かると思うんです。単体テストを考えると低い方が良いです。
まあ、結合テスト以降は関係なくなるのですが、単体テストが面倒くさくなると品質が落ちて、結合テスト以降のテストにも影響してきます。
強度(凝集度)なのですが、強度が低い場合は結合度が高くなっていることが多いみたいなんです。
また、強度が一番低いと修正が必要になりやすいです。
「モジュールの強度(凝集度)を上げ、結合度を下げる設計にする」のは、言うのは簡単ですが、実際には難しい場合があります。GRASPパターン、GoFのデザインパターンを勉強する必要があります。
参考:
「だるまのエクセルVBA」-「オブジェクト指向についてまとめたもの」-「オブジェクト指向とモジュールの凝集度、モジュールの結合度」
「だるまのエクセルVBA」-「オブジェクト指向についてまとめたもの」-「オブジェクト指向設計とGRASPパターン」
「だるまのエクセルVBA」-「オブジェクト指向についてまとめたもの」-「デザインパターンとアンチパターン」
■3.[設計時]モジュールの相互参照をやらないようにする
相互参照すると、単体テストが面倒くさくなります。
結合テスト以降は関係なくなるのですが、、以下略。
「モジュールの相互参照をやらないようにする」のは、言うのは簡単ですが、実際には難しい場合があります。GRASPパターン、GoFのデザインパターンを勉強する必要があります。
参考:
「だるまのエクセルVBA」-「オブジェクト指向についてまとめたもの」-「オブジェクト指向とモジュールの凝集度、モジュールの結合度」
「だるまのエクセルVBA」-「オブジェクト指向についてまとめたもの」-「オブジェクト指向設計とGRASPパターン」
「だるまのエクセルVBA」-「オブジェクト指向についてまとめたもの」-「デザインパターンとアンチパターン」
■4.[設計時]変更を受けにくい箇所と受けやすい箇所で、モジュールを分ける
変更を受けると、テストプログラムにも修正が必要になります。
修正の範囲を減らせれば良いですねってことです。
BCEパターンを勉強しましょう。
参考:
「だるまのエクセルVBA」-「オブジェクト指向についてまとめたもの」-「エンティティクラス、バウンダリクラス、コントロールクラス、その他のクラス」
■5.[設計時]テストの自動化が行いやすいところ、行いにくいところでモジュールを分ける
言っていることは、「4.[設計時]変更を受けにくい箇所と受けやすい箇所で、モジュールを分ける」に近いです。
すべてのテストの自動化が無理でも、一部でも行えれば、良いですね。で、その一部の範囲を広げましょうと言うことです。
■6.[設計時]再現性が低くなるような箇所(他のプロセス、スレッド、DBのselect、メモリ破壊など)に注意して設計する
再現性が低い不具合は、発見が難しくので、設計段階から対策を取っておく必要があります。
複数のプロセス、スレッドが動作する場合は、タイミングの問題で再現性が低い不具合が起きやすいです。
DB(データベース)のselectは、複数レコード取得する場合は、order byを付けておかないと並び順が定義されない場合があります。そうすると、再現性が低くなるので、DBのマニュアルを見て、必要であればつけるようにしましょう。
メモリ破壊で思いつくのはバッファーオーバーラン、スタックオーバーフロー、2重解放です。あと、メモリリークにも注意意が必要です。これは、プログラミング時にも注意が必要だけど、設計段階から考えよう。
バッファーオーバーランはC/C++で起こりやすいです。関数化して、範囲チェックしてから使うようにすれば、防げます。
スタックオーバーフローも、C/C++で起こりやすいですが、JavaやVBAでも起きます。C/C++の場合は関数内に巨大な配列をローカル変数で定義するとスタックメモリが一気になくなります。mallocやstd::vectorを使うなりして、ヒープメモリからメモリを確保してください。
あと、C/C++、Java、VBAに共通なのですが、関数(メソッド)の再起呼び出しは危険ですよ。止めましょう。
2重解放はC/C++で起きやすいです。Cの場合は対策が難しいですが、取得(malloc)と解放(free)は同じモジュール内にしておいたほうが良いです。C++の場合はシェアードポインタの使用を検討してください。
メモリリークはC/C++、Java、VBAに共通です。C/C++では取得した後の解放漏れが多いです。特に異常系のときに解放漏れしない設計にしましょう。
C/C++、Java、VBAに共通だけど、グローバル変数に可変長の配列を作成し、そこに動的に確保したメモリ領域を格納し、必要なくなったのに解放しないのもメモリリークです。
そうならないように注意して設計してください。Javaの場合、java.util.WeakHashMapの使用を検討しよう。
■7.[設計時]ログ出力が遅いと、かなりまずいので、速度を考える
ログ出力を、すでにあるライブラリ(JavaならLog4Jがある)を使うなら、あまり考えなくて良いのですが、使えるものがなくて自作する場合、Log4Jのようなライブラリを使うが一部を自作する場合(自作のAppenderを使う場合)は、速度に注意しましょう。
間違っても、1行書き込みをするたびにファイルオープン・クローズなんてしないように!(過去3回あった!)
これをやったら、べらぼうに遅くて使い物にならなくなります(ハードの性能によるが、過去、この方法で1秒間に100行程度しかログ出力できなかったことがあります)。
ファイルは開きっぱなしにして、1行書いたら、フラッシュしよう。これで50倍くらい高速化したことがあります。
もし、これでも速度に問題があれば、書き込みとフラッシュをハードディスクあるいはSSD(ソリッドステートドライブ)のセクタの容量単位で書き込むと速くなります。その代わり、ログの消失を覚悟する必要があります。
ログの出力レベルは設定ファイルによって変更できるようにすることが多いと思います。が、この設定は、クラスごとに行えるようにしておかないと、厄介なことになる場合があります(単体テスト実施時に、自分のところに関係ないところのデバッグログが大量に出力されて、困る)。気をつけよう。
■8.[設計時]ログの出力項目にも注意を払う
例えば、出力時刻、スレッド名、スレッドID、ログ出力場所(クラス名やメソッド名、行番号)はあったほうが良いです。
念のため言うが、出力時刻は出来れば、ミリ秒までですよ。
Javaの場合、ログ出力場所の出力は速度低下につながるので、難しいかもしれませんが、C/C++ならマクロ(__FUNCTION__と__LINE__)で出来るから、速度低下は無いから、出力しよう。VBAはごめん、知らない。
■9.[設計時]プログラムが強制終了したときのことも考えよう
すみません。VBAのときは知らないのですが、C/C++であれば、コアダンプが出力できないか確認しよう。
Javaなら、起動時の引数に「-XX:+HeapDumpOnOutOfMemoryError」とか「-XX:OnError」とかを付けるとかする。詳しいことはJava HotSpot VM Command-Line Optionsを見てください
■10.[プログラミング時]再現性が低くなるような不具合(メモリリーク、メモリ破壊など)に注意してプログラミングする
設計時にも注意して欲しいですが、プログラミング時にも注意が必要です。
■11.[プログラミング時]理由が無い限り、1つの関数の行数が大きくならないようにする
1つの関数の行数が大きくなると、単体テストが大変になります。また、C++の場合、try-catchのtryの行数が多くなり、catch内でログ出力したときにどこが原因でcatchされたのかが分かりにくくなります。
※C++の例外は、Javaの例外と違って、スタックトレースがありません。
VBAもスタックトレースが無いから、1つの関数を短くし、On Error GoToからエラーキャッチ部分の行数を短くしておく必要があります。
■12.[プログラミング時]エラー処理をちゃんと行って、エラー発生時にちゃんとログを出力する
当たり前だけど、まずはエラー処理してくれ。C言語の場合は例外スローと言う概念がないので、成功・エラーは戻り値で判断することが多いです。VBAからOSが提供するAPIを呼び出す場合も同じです。
C++は例外をスローすることが多いですが、Qtの様に例外をスローしないライブラリもあるのでマニュアルを読む必要がある。Javaの場合は、ほとんど例外がスローされる。ただ、RuntimeExceptionなのかそうでないのかは確認した方が良いですね。
で、エラー処理にてログ出力する場合は、エラーの原因を出力してください。
C言語の場合は、関数がエラー値を返した場合、その戻り値がエラーコードになっている場合もあれば、GetLastError関数(Windows API)を呼び出すとか、errnoを取得する(POSIX)を行う必要がある場合があります。これはやってください。
それから、エラーが発生したときの状態(関数の引数や、フィールドの値)をログに出力してもらえると、不具合の解析が楽になります。セキュリティの問題が無い限り(例えば、ログファイルにユーザのパスワードを出力するのはまずいだろう)は、出力しましょう。
■13.[プログラミング時]InfoログやDebugログもちゃんと出力する
ユーザ操作が行われたときはInfoログを出力しても良いと思う。
Debugログはご自由に。
■14.[プログラミング時]1つ行で何個も関数コールしない
Javaでは
CCC ccc = xxx.getAAA().getBBB().getCCC();
の様なコードが記述できてしまいます。C++やVBAでもほぼ同じかな。
上記の書き方は良くありません。
理由です。例えば、上記のgetAAA()の戻り値がnullだったら、NullPointerExceptionがスローされる。getBBB()の戻り値がnullのときもNullPointerExceptionがスローされるのですが、スタックトレース内の行番号から区別が付かないのです。
AAA aaa = xxx.getAAA();
BBB bbb = aaa.getBBB();
CCC ccc = bbb.getCCC();
と書かた方が良いです。
他には、
DDD ddd = xxx.getDDD( yyy.getUUU() , zzz.getIII() );
の様に関数(メソッド)の引数に関数(メソッド)の戻り値を直接設定する書き方ができます。これはC/C++,VBAでもほぼ同じです。
これもあまり良くないです。Javaなら先ほどの例外内のスタックトレースにて区別できないと言う問題は発生しにくいですが、デバッグ実行中に引数の値を変更しにくいと言う問題があります(C++,VBAでも同じ)。なので、
UUU uuu = yyy.getUUU();
iii iii = zzz.getIII();
DDD ddd = xxx.getDDD( uuu , iii );
と書いたほうが良いです。
■15.[テスト時]テスト用のツールを勉強する。特にスタブ用のツール。
勉強嫌いなら、すまん。
JavaならJUnitやJMockit、EclEmmaです。
言語には依存しないけど、画面の自動化でUWSCとか。
VBAはすみません。知らないです。
■16.[テスト時]複数のテストを一度に行うテストプログラムの作成はほどほどに
やりすぎると、テストプログラムを他の人が理解するのが大変になるので。
■17.[テスト時]テストドライバやスタブの中でもログ出力する
必要になることが多いです。
なんか、内容が難しくてすみません。
このページを作成するのに参考にした書籍です。
このページの利用によって発生した、いかなる損害について、このホームページの作成者は責任を負いません。
このページの間違いや嘘を見つけた方、このページに書いて欲しい情報がある方は
メールをお願いします。
Microsoft 、Windows 、Visual Basic および Excel は米国Microsoft
Corporationの米国およびその他の国における登録商標または商標です。
ここではExcel® をエクセル、Visual Basic® for Applications をVBAと表記する場合があります。
Mac 、Mac OS 、Mac OS X は米国Apple
Computer,Inc.の登録商標または商標です。
その他、社名および商品名、システム名称などは、一般に各社の商標または登録商標です。
このホームページの作成者はこれらの会社とはいっさい関係がありません。