優れたオンラインリソースを指すことはできません(これらのトピックに関するウィキペディアの英語の記事は改善される傾向があります)が、基本的なテスト理論についても説明した講義を要約できます。
テストモード
単体テストや統合テストなど、さまざまなクラスのテストがあります。単体テストでは、コヒーレントなコード(関数、クラス、モジュール)自体が期待どおりに機能することを確認しますが、統合テストでは、このような複数の部分が正しく連携することを確認します。
テストケースとは、特定のテスト入力を使用したり、他のクラスをモックしたりすることにより、コードが実行される既知の環境です。次に、コードの動作を、特定の戻り値などの予想される動作と比較します。
テストでは、バグの存在のみを証明できますが、すべてのバグが存在するわけではありません。テストはプログラムの正確性に上限があります。
コードカバレッジ
コードカバレッジメトリックを定義するために、ソースコードを制御フローグラフに変換して、各ノードにコードの線形セグメントを含めることができます。これらのノード間の制御フローは各ブロックの最後でのみ行われ、常に条件付きです(条件の場合はノードAに、そうでない場合はノードBに移動します)。グラフには、1つの開始ノードと1つの終了ノードがあります。
- このグラフでは、ステートメントカバレッジは、訪問したすべてのノードとすべてのノードの比率です。完全なステートメントカバレッジは、徹底的なテストには不十分です。
- ブランチカバレッジは、すべてのエッジに対するCFG内のノード間のすべての訪問済みエッジの比率です。これではループのテストが不十分です。
- パスカバレッジは、すべてのパスに対する訪問済みのすべてのパスの比率です。パスは、開始ノードから終了ノードまでのエッジのシーケンスです。問題は、ループでは無限の数のパスが存在する可能性があるため、完全なパスカバレッジを実際にテストできないことです。
したがって、条件カバレッジを確認すると便利な場合があります。
- では、簡単な条件カバレッジ、各原子の条件が真と偽のワンスである-しかし、これは完全なステートメントカバレッジを保証するものではありません。
- では、複数の条件カバレッジ、アトミックな条件は、すべての組み合わせで撮影してきた
true
とfalse
。これは完全なブランチカバレッジを意味しますが、かなり高価です。プログラムには、特定の組み合わせを除外する追加の制約がある場合があります。この手法はブランチカバレッジを取得するのに適していますが、デッドコードを見つけることはできますが、間違った状態に起因するバグを見つけることはできません。
- で最小複数の条件カバレッジ、各原子及び複合条件が真と偽回です。それはまだ完全なブランチカバレッジを意味します。複数の条件カバレッジのサブセットですが、必要なテストケースは少なくなります。
条件カバレッジを使用してテスト入力を構築する場合、短絡を考慮する必要があります。例えば、
function foo(A, B) {
if (A && B) x()
else y()
}
でテストする必要がありfoo(false, whatever)
、foo(true, false)
およびfoo(true, true)
完全な最小限の複数の条件カバレッジのために。
複数の状態にできるオブジェクトがある場合、制御フローに類似したすべての状態遷移をテストするのが賢明なようです。
いくつかのより複雑なカバレッジメトリックがありますが、それらは一般的にここに示されているメトリックに類似しています。
これらはホワイトボックステスト方法であり、部分的に自動化できます。ユニットテストスイートを持ってすることを目指すべきであることに注意してください高を、選択したメトリックによってコードカバレッジますが、100%が常に可能であるとは限らない。特定の場所にフォールトを挿入する必要がある例外処理をテストすることは特に困難です。
機能テスト
次に、実装をブラックボックスとして表示することにより、コードが仕様に準拠していることをアサートする機能テストがあります。このようなテストは、単体テストと統合テストの両方に役立ちます。考えられるすべての入力データでテストすることは不可能なので(たとえば、考えられるすべてのストリングで文字列の長さをテストする)、入力(および出力)を同等のクラスにグループ化すると便利ですlength("foo")
。foo("bar")
おそらく仕事に同様です。入力と出力の等価クラス間の可能な組み合わせごとに、少なくとも1つの代表的な入力が選択され、テストされます。
さらにテストする必要があります
- エッジケース
length("")
、foo("x")
、length(longer_than_INT_MAX)
、
- 言語で許可されているが、機能の契約では許可されていない値
length(null)
、および
- 可能性のあるジャンクデータ
length("null byte in \x00 the middle")
…
数値では、これはテストを意味します 0, ±1, ±x, MAX, MIN, ±∞, NaN
、浮動小数点の比較では2つの隣接するフロートをテスト。別の追加として、ランダムなテスト値を等価クラスから選択できます。デバッグを容易にするために、使用したシードを記録する価値があります…
非機能テスト:負荷テスト、ストレステスト
一部のソフトウェアには機能以外の要件があり、それらもテストする必要があります。これらには、定義された境界でのテスト(負荷テスト)と、それを超えたテスト(ストレステスト)が含まれます。コンピュータゲームの場合、これは負荷テストで1秒あたりの最小フレーム数をアサートしている可能性があります。Webサイトは、予想される2倍の訪問者がサーバーを攻撃している場合の応答時間を観察するために、ストレステストされる場合があります。このようなテストは、システム全体だけでなく、単一のエンティティにも関連しています-ハッシュテーブルは、100万エントリでどのように低下しますか?
他の種類のテストは、シナリオをシミュレートするシステム全体のテスト、または開発契約が満たされたことを証明する受け入れテストです。
非テスト方法
レビュー
品質保証に使用できる非テスト手法があります。例は、ウォークスルー、正式なコードレビュー、またはペアプログラミングです。一部の部品は自動化できますが(たとえば、リンターを使用して)、これらは一般に時間のかかる作業です。ただし、経験豊富なプログラマーによるコードレビューはバグの発見率が高く、自動テストが不可能な設計時に特に価値があります。
コードレビューが非常に優れているのに、なぜテストを作成するのですか?テストスイートの大きな利点は、(ほとんど)自動的に実行できることであり、回帰テストに非常に役立ちます。
正式な検証
正式な検証が行われ、コードの特定のプロパティが証明されます。手動検証は、プログラム全体ではなく、重要な部分に対してほとんど実行可能です。証明により、プログラムの正確性に下限が設定されます。証明は、静的型チェッカーなどを介して、ある程度自動化できます。
特定の不変式は、assert
ステートメントを使用して明示的にチェックできます。
これらの手法はすべてその場所にあり、補完的なものです。TDDは機能テストを事前に記述しますが、コードが実装されると、カバレッジメトリックによってテストを判断できます。
テスト可能なコードを作成するということは、個別にテストできる小さなコード単位を作成することを意味します(適切な粒度、単一の責任原則を持つヘルパー関数)。各関数が取る引数が少ないほど良い。このようなコードは、依存オブジェクトの挿入などを介して、モックオブジェクトの挿入にも役立ちます。
double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }
から学んだこの古典的な例を考えてみましょう。このコードには、ブラックボックステストだけでは自動的に発見できないホールが1つだけあります。数学では、そのような穴はありません。微積分では、片側の制限が等しい場合、穴を閉じることができます。