組合せテストの考え方

アドベントカレンダー５日目です🎉
引き続き@mgmgOmOが担当します！

今回は、テストの基本である**組合せテスト**のやり方について書きます。
ソフトウェアテストについて勉強していた時期があり、聞かれることが時々あったので自分なりに持っている知見をまとめていきます。
テスト設計ってなんとなくやっちゃいがちですが、体系立てて学ぶことでスキルとして身につけることができます。
[ソフトウェアテスト入門-押さえておきたい-要点・重点-](https://www.amazon.co.jp/dp/4774134546)の本をベースにしています。

# 前提
例として、楽天市場の商品検索フォームに対するE2Eテストを考えます。
<img width="597" alt="フォーム.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/9bd0bb99-c370-8b94-2d28-5c1101a7c53f.png">

組合せテストは以下のような流れでテストケースを作成していきます。

1. 因子を洗い出す
2. 水準を洗い出す
3. 組合せを考える
4. 期待値を考える

## 1. 因子を洗い出す
**因子**とは、つまり条件のことです。
ここでは、フォームの項目が挙げられます。

|因子|
|:------------:|
| ジャンル |
| キーワード |
| キーワード検索方法 |
| 商品名・商品番号を含むか |
| 除外キーワード |
| 翌日配達エリア |
| 価格帯下限 |
| 価格帯上限 |
| 絞り込み条件 |
| 並び替え |


## 2. 水準を洗い出す
**水準**とは、因子の値のことです。
水準には基本的に異常系パターンを含みません。例えば価格の項目に対するマイナス値などです。
こういうものは単体テストでチェックしているはず！なので除外します。
(単体テストが信用できない場合は入れてもいいかも・・・笑)

水準は、ものによっては無限大の解答が出てきます。例えば文字列や数字がそうですね。
数字を入れるフォームに、1,2,3,...と順番に入れてテストしていっても非効率です。
そこで**同値分割・境界値分析**を行って代表値を選びます。
また、ジャンルやエリアの数も多いので、代表値として大分類の中から一つ選ぶことにします。
代表値を選ぶ場合は、後から見た時にわかるように観点を添えておくのがオススメです。

|因子|水準１|水準２|水準３|水準４|水準5|...|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| ジャンル |指定なし|(ファッション)レディースファッション|(家電・パソコン・通信)スマートフォン・タブレット|(食品・飲料・お酒)食品|(インテリア・日用雑貨)インテリア・寝具・収納|...|
| キーワード |指定なし|任意の文字列|
| キーワード検索方法 |すべて|いずれか|
| 商品名・商品番号を含むか |ON|OFF|
| 除外キーワード |指定なし|任意の文字列|
| 翌日配達エリア |指定なし|北海道|(東北)青森|(関東)東京|(甲信越)新潟|...|
| 価格帯下限 |指定なし|0(最小値)|999999999(最大値)|
| 価格帯上限 |指定なし|0(最小値)|999999999(最大値)|
| 絞り込み条件 |指定なし|送料無料|カードOK|レビューあり|ギフト対応可|...|
| 並び替え |標準|価格が安い|価格が高い|新着順|レビューの件数が多い|

個人的には、検索キーワードと除外キーワードが同じ文字列の場合と、下限の価格が上限の価格より高い場合にどういう挙動をするのか気になります。
こういう矛盾した部分にバグが潜みやすいですよね。

## 3. 組合せを考える
単純に全ての組合せをテストするのがベストです。
問題は、全ての組合せの総数=因子1の水準数×因子2の水準数×因子3の水準数...
・・・そんなに多くの数のテストをやっていられないですよね。
そのため、以下のように間引くことを考えます。

(1) **禁則**と呼ばれる起こりえない組合せを除外
(2) 影響度を考慮して除外
(3) **直行表**や**オールペア法**を用いて除外

#### (1) **禁則**と呼ばれる起こりえない組合せを除外
禁則は、例えばデバイスAndroid+ブラウザSafariといった条件の組合せです。
今回は特にないので飛ばします。

#### (2) 影響度を考慮して除外
影響度は、本当はすべてのテストを行うのがいいけど、組合せのテストケースはすぐに増大してしまうので**品質・時間・予算のバランス**を見極めて相談しつつ削減していきましょう。
例えば、納期が迫ってるので特定の組合せを除外する？その場合のリスクは許容できる？そもそも組合せテストが費用対効果的に適切？など。
しないテストケースは**理由**を添えて明確に。
これも今回は飛ばします。

#### (3) **直行表**や**オールペア法**を用いて除外
影響度もどれも同じ場合は、直行表やオールペア法、ペアワイズ法などの技法を用いて論理的に削減します。
今回は、一番よく使われる**直行表**を用いて組合せを行います。
直行表は二因子間網羅とも呼ばれ、2因子間のすべての組み合わせを確かめれば良いという考え方に基づいています。

<img width="1220" alt="スクリーンショット 2019-12-03 22.23.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/a23b6d25-882d-ce56-f9f6-4c4b7b39dc18.png">

作成方法は省略しますが、このような組合せになります。

## 4. 期待値を考える
期待値はそのまま組合せに対してくっつけていきます。
そうするとテストケースっぽいものが出来上がり！
<img width="1328" alt="スクリーンショット 2019-12-03 22.35.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/128ff859-e8e6-06ce-8456-6728e2b2338c.png">

面倒なので全部「検索結果が条件に合致する」とか書いちゃいましたが、**悪い例**です。笑
期待値に**「適切」「正しい」「合致」**などのキーワードはなるべく無くして**どういう状態が正しいのか**を書きましょう。
例えば何も指定せずに検索した場合に「検索キーワードが空欄です。キーワードを入力して検索してください。」という表示がでます。
この例では仕様だとわかるとは思いますが、その結果は期待通りでOKなのか、それともエラーでNGなのかどうか判断しづらくなってしまうためです。

あと、水準に「任意の文字列」とか書いちゃってますが、これもなんでもいいので具体的な文字列を明記するべきです。
どういう条件でテストしたかわからなくなってしまうので。
**曖昧な表現はなるべく避ける**ように頑張りましょう。(自戒を込めて)

テストケースをレビューしてもらう場合は、因子・水準の表も一緒に提出すると抜け漏れが発見しやすくなるので親切だと思います。

## 余談
今回の例だと単体テストをちゃんとやっていればここまで厳密にする必要もない気はしますが、やり方を知っていれば色々応用が効くのでぜひ試してみてください。

直行表に関する記事はこちらも参考になります。
[組み合わせテストを科学的に効率化する――手法とツール、品質保証のための道具 (1/3)：現場で使うためのオールペア法、直交表の基本（2） - ＠IT](
https://www.atmarkit.co.jp/ait/articles/1503/16/news149.html)

状態によって期待値が変わる場合は**状態遷移図**や**ディシジョンテーブル**などの技法を使うのもおすすめです。

テスト設計する際は、**仕様に書いてないところ**にバグが潜むことが多いので、それを意識すると良さげです。
コツとしては設計中に自分自身に心の中で**ツッコミ・問いかけ**していくのが有効です。
「これをする/しない場合はどうなるの？」のような感じで・・・
やりすぎると進捗に影響があるので適度に。笑
あとはやっぱり、**実装していない人に見てもらう**のがいいのかなと思います。自分にない観点からの気づきがあったりするので。
また、テクニックに頼りすぎるとそれにしか意識が及ばなくなる可能性があるので、**あくまで一つの手段**という位置付けで利用するのが良いです。

今回は正常系のみの解説でしたが、個人的に**異常系**の方が大事だと思ってます。
・文字だと、特殊文字、絵文字、JSやSQLのインジェクション
 (参考：[【テスト入力パターン集】Webフォームの単体テストでチェックすべき18のポイント](https://www.bricoleur.co.jp/blog/archives/4239))
・数字だと、マイナス、小数点、異常に大きい数
・ありえない数のリクエスト数
・複数人が同じタイミングで操作
・通信中にアプリをシャットダウンまたはPCの電源を切ってみる
など、**ぶっ壊す気持ちで**やってみるとちょっと楽しいかも？笑
負荷検証やセキュリティテストはそれだけで範囲が広く重いので、別途に行うケースも多いです。
基本的なことですが、異常値は入力できないように設計するか、入れられたとしてもユーザにその旨を通知する実装にしましょう。

**テストに関するイベント**としては、[JassT](http://www.jasst.jp/)、[WACATE](https://wacate.jp/)が有名です。
昔まとめた[JassT Review'18のレポート](https://qiita.com/mgmgOmO/items/a2014d43cac1a3699b39)の記事もあるのでよかったら見てください。

品質のいい製品を目指していきましょー！おしまい！