初めて学ぶソフトウェアのテスト技法
この本は手動テストのための本だが、自動テストをするときにどうすれば網羅性を担保できるか?という観点から読んでみる
対象読者には「ソフトウェア開発者」が入ってるが、手動テスト前提っぽい記述がそこかしこにあるので、手動テストの人向けなのかなと思った
ブラックボックステスト
同値クラス
同値クラス
入力となる値のグループであり、各グループごとにロジックが分岐するようなもの
ある同値クラスの中の値でバグったら、同じ同値クラスの中の値は全部バグる
ある同値クラスの中の値でバグらなかったら、同じ同値クラスの中の値は全部バグらない
契約によるテスト/防御的テスト
事前条件と事後条件
事前条件:そのメソッドを呼び出すときに満たすべき条件
事後条件:そのメソッドが呼び出されたあとに満たすべき条件
契約によるテスト:事前条件が満たされている前提でテストする
防御的テスト:事前条件が満たされないケースもテストする
これはつまり「invalidな入力」を事前条件の中に入れ、そのとき「例外を返す」という事後条件を追加して、契約によるテストを行うということ
有効値と無効値に関するテスト
パラメーターがn個あるモジュールについては、以下のn+1個のケースをテストする
全部のパラメータが有効値であるケース
各パラメータについて、それ1つだけが無効値で他は全部有効値であるケース
感想
同値クラスを見つけ出すには経験と、あとはテスト対象モジュールの中で使うモジュールの仕様をちゃんと理解することが重要そう
例えば以下のコードはarrが空のときにnilを返すので、[]と[1]がそれぞれ別の同値クラスに属する値だけど、これはsumの挙動を理解してないとわからない。この例は簡単というか、「一般的に配列はemptyのときとそれ以外で挙動が変わる」とかゼロ値とそれ以外で属するクラスが変わる、みたいな知識で対応可能だけど、そうじゃないものもありそう
逆に言うと、それが直感的にわかるようなモジュール・命名をするとテストしやすくなりそう
code:ruby
def calculate_sum(arr)
arr.sum
end
防御的か契約的か、という問題については以下のように使い分けるのが良さそう
外部に露出するI/Fについては防御的に作り、テストする
内部でしか使われないI/Fについては契約的に作り、テストする
疑問
有効値と無効値の同値クラスについての実例はあるけど、有効値の中でさらに同値クラスが分かれるときにどうするか、という問題に対する例がない
各パラメータを複合した同値クラスを考える、みたいなことになりそう
例えば、A、B、CというパラメータがあってそれぞれA1,A2, A3, B1, B2, B3...のような同値クラスがあり、A x B < 100のときとそれ以外のときでロジックが分岐するみたいなときは、A x B < 100 or notが1つの同値クラスになる。これをA、B、Cについて分析し、A、B、Cを複合した同値クラスをみつける。
例えば以下のようなイメージ
A x B < 100
A x B >= 100 && C < 5
A x B >= 100 && C >= 5
同値クラステストはブラックボックステストのはずなんだけど、割と内部の実装にべったりなテストになる雰囲気がする。
同地クラステストをちゃんとやろうとすると、如何に特殊な分岐を減らせるかの勝負になりそう。特殊な分岐を減らして、同値として扱える範囲を増やすのが大事っぽい。
境界値
同値クラスの境界について、「境界のすぐ下」「境界上」「境界のすぐ上」の3パターンをテストする
疑問
同値クラスのときと同様に、組み合わせについてあまり言及がない
唯一、2つのパラメータについて2次元の図で、説明しているものがあったが、網羅的な原則を説明しているわけではなかった
ディシジョンテーブル
入力値の全組み合わせに対して、期待される出力を表形式で記述する。仕様の一部をなす。
デシジョンテーブルはテストにも開発にも使える
「ルール」がテストケースに対応する
各ルールについて、各条件の下端・上端のテストを行う
デシジョンテーブルの例
table: example
ルール1 ルール2 ルール3 ルール4 ルール5 ルール6 ルール7 ルール8
条件A 100 以下 100以下 100以下 100以下 100より大 100より大 100より大 100より大
条件B 10以下 10以下 10より大 10より大 10以下 10以下 10より大 10より大
条件C 5以下 5より大 5以下 5より大 5以下 5より大 5以下 5より大
アクションX 成功 成功 成功 成功 成功 成功 成功 成功
アクションY 失敗 失敗 失敗 失敗 失敗 失敗 失敗 成功
開発観点でのデシジョンテーブル
Don't care(なんでもいい)の概念を導入すると、以下のようにルールを整理できる
table:example
ルール1 ルール5 ルール7 ルール8
条件A 100 以下 100より大 100より大 100より大
条件B DC 10以下 10より大 10より大
条件C DC DC 5以下 5より大
アクションX 成功 成功 成功 成功
アクションY 失敗 失敗 失敗 成功
テスト視点でのデシジョンテーブル
DCによってルールを整理してはならない
なぜなら実装のバグを見つけられなくなるため
デシジョンテーブルからテストケースを作る
table:example
ルール1 ルール1 ルール2
テストケース1-1(全部上端) テストケース1-2(全部下端)(実際には下端は存在しないのでなし) ...
条件A 100 ... ...
条件B 10 ... ...
条件C 5 ... ...
アクションX 成功 ... ...
アクションY 失敗 ... ...
感想
デシジョンテーブルとparameterized testを組み合わえると、自動テスト観点で以下のようなことができそう
デシジョンテーブルをDCを使って縮小する
各ルールについて以下を行う
DCについて、境界値を列挙する(ここはランダム値 or nullでもよさそう)
DCでない条件について、上端・下端を列挙する
↑で上げた全てのパターンについて、parameterized testを行う
これの何が嬉しいかと言うと、開発観点とテスト観点でのデシジョンテーブルの使い方を統合できる
開発観点では、デシジョンテーブルをDCで縮小したほうがわかりやすくなるので縮小したい
テスト観点では、デシジョンテーブルをDCで縮小するとバグを検出できない可能性があるので縮小したくない
上記手法によって、コード上では縮小したテーブルを扱いながら、行われるテストとしては縮小されていない全パターンを網羅的にテストできる
ペア構成テスト
入力パラメータの数が多すぎて、組み合わせの全パターンを網羅的にテストするのが難しいときに、効果的な値の組み合わせを選定する方法
全組み合わせについて検証するのではなく、「入力パラメータのどの2つについても、その2つのパラメータが取りうる全ての値の組み合わせが現れる」ような組み合わせを検証する、という手法
根拠
多くの組み合わせテストで欠陥が発見されるとき、1) ある1つのパラメータが特定の値を取ったとき動かない 2) ある2つのパラメータが特定の値の組み合わせをとったとき動かない のいずれかになることがほとんどである、と経験的に知られている
「ある3つのパラメータが特定の値の組み合わせを取ったときだけ動かない」となることはほぼない、という主張をしている
感想
この本は主に手動テストについての本なので、テスト対象の組み合わせ数を減らすことに大きな関心があるということから、これを取り上げているのだと思う
自動テストは基本的に高速なので、組み合わせ数を減らすことにはそれほど大きなモチベーションはない。また、この手法を用いるとテストの可読性と保守性が下がることが予測されるため、使わないに越したことはないだろう。
全パターンテストしたいが組み合わせの数があまりにも多すぎる場合や、1テストケース実行するのに長い時間がかかるような場合に有効かもしれない。
状態遷移テスト
状態の遷移を伴うシステムには、仕様を策定するタイミングで状態遷移図や状態遷移表を作るとよい
状態遷移図
丸と矢印で書くアレ
丸は状態を表す
矢印はイベント(hogeボタンが押された、特定の時刻に達した、など)と、アクション(メール通知を行う、料金を払い戻す、など)を表す
状態遷移表
状態、イベント、アクション、次の状態を表にしたもの
無効な状態遷移についても記述するため、一般にsparseな表になる
網羅的に状態遷移を検討するのに使う
table:examle
状態 イベント アクション 次の状態
State A Event 1 Event 2
State A Event 2 -
State A Event 3 -
状態遷移のテスト
スタンダード:全ての遷移(矢印)を最低一回通るようなパスの集合を検証する
気合を入れる:全てのパスを通るように、パターンを列挙する
パス:開始してから終わるまでの、遷移の組み合わせ
A -> B -> C or D という遷移図だったら、A->B->CとA->B->Dがそれぞれパス
ループがあるタイプの状態遷移だと、全てを網羅できない可能性がある
ただし、ループを何回も回して死なないことを担保するのが必要になる場合もある
感想
状態遷移のテストに関する指針が示されるのは助かる
特に、全パス探索は多くの場合やりすぎというのが示されたのは大きいと思う
ドメイン分析テスト
同値クラス・境界値テストをn次元に拡張したようなもの
「ドメイン」とは業務領域のことではなく、空間における領域のこと
入力変数がn個、条件式がm個あるとする。このとき、領域における境界線(3次元以上なら面)はm個ある。
変数X1, X2, X3, X4, ...Xn
条件式C1, C2, C3, ..., Cm
条件式とは、f_C1(X1...Xn) ( < | <= | > | >= | = ) 定数 となるような式
「領域」とは、全ての条件式を満たす点の集合
「境界」とは、各条件式について f_Cn(X1..Xn) = 定数 となる点の集合
このとき、inポイント、onポイント、offポイントを以下のように定義する
inポイント
領域に属し、境界上ではない点
onポイント
各条件式の境界上の点
offポイント
各条件式について
<や>の場合、境界に隣接しかつ領域に含まれる点
<=や=>の場合、境界に隣接しかつ領域に含まれない点
= の場合、境界に隣接する両隣の点
つまり、onとoffを全てチェックすると、境界について含まれる・含まれないが網羅的に検証できる
テストケースは以下のように作る
各条件式について、「領域に隣接し、かつその条件式については on | off」の点を検証する
感想
なんか本に書いてある例の表がおかしい気がする...