状態によって変化(消えるものがあったり、代わりに現れるものがあったり)するUI、あるいは触ること(変更操作)ができなくなったりするUIが含まれる場合の組合せテスト設計方法

# テスト対象のUIに着目してテストを設計している時に、困ったことはありませんか？

テスト設計において、UIの入力項目(因子)を洗い出し、そしてその各入力項目にどんな値を入力してみるかを考えたり(同値分割して水準を決める)、その与える値の組み合わせ方を考えていくようなやり方は、お手軽に始められることもあり、現場で採り入れられることが多いと思います。
さて、もしもそこに何の工夫もなければ、もはやそのようなテストはAIに任せた方が良い、という時代が到来しつつあるようにも思います。

しかし、ここにはAIに頼るよりも確実(網羅的)で、しかも機械的な手順でできる効率的な設計方法があります。[ペアワイズ法などのテスト設計ツール](https://qiita.com/sho1884/items/db1662e75dbf84fccc94)を用いれば良いのです。ツールが組み合わせを自動生成してくれるので、とても楽ちんです。しかも、何をどれだけ網羅的にテストしたのか、[関係者に分かりやすく説明できます](https://tech.nikkeibp.co.jp/it/article/COLUMN/20140311/542613/)。
これらのツールにより自動生成されるテストケースは、テスト項目数としては少ないですが、組み合わせ網羅率が高いので、バグ検出率が高い良質なものが得られます。
さらに各テストケースで与える値の並びが揃っているので、データ駆動によるテスト自動化にも都合が良く、これを上手く使いこなさない手はありません。

ところで、入門書等の例題的なものはともかく、実務で扱うテスト対象に適用しようとすると、禁則などと呼ばれることもある制約関係があるのが普通です。その情報もツールに入力してやる必要があります。これは少し面倒な作業だと感じられることもありますが、テスト対象の仕様を確認して入力していけば良いだけの作業です。仕様の情報を与えずにテスト設計しろというのは無理な話なので、残念ながらこの作業は避けて通れません。ただ、一つ一つ様々なテストケースを、しかも全体として網羅的になるように、自分で考案しながら書き上げることに比べれば、ずっと楽な作業であると言えるでしょう。

さて、いよいよ本稿のテーマになります。その禁則の取り扱い方についてですが、これまで文献などであまりちゃんと説明されたことがない部分が残っていることに気が付きました。そこでここではその説明をすることにします。

それは、**「状態によって消えたり現れたり、触ること(操作)ができなくなったりするUIをどう扱うか」**ということです。
(「あれっ、それって既によく説明されているいわゆる禁則関係のことだよね！」と思った方はいらっしゃいますか？　確かにそうなのです。が、禁則関係の中でもさらに特殊なものになっている点、そして**その取扱い方**を問題にしている点に注意してください。「今までその事で困ったことなどない。どこが特殊だというのかね？」と思った方には特に、この記事は読む価値があると思います。)

# 「状態によって消えたり現れたり、触ること(操作)ができなくなったりするUIの扱い」はどう特殊なのか？

例えば、クレジットカードでの支払いを選択するとカード番号の入力欄が出てきますが、別の支払い方法を指定するとカード番号入力欄そのものが消えたりします。

このような入力項目(因子)の取り扱い方法に悩んで、なかなか先に進めなくなってしまう現場もあるようです。

さて、やり方はいくつか考えられるのですが、本稿で一つの方法を示すことにします。

具体例として、書籍[『ソフトウェアテストHAYST法入門』](https://www.amazon.co.jp/dp/4817192283/)で禁則の説明のために取り上げられているものと同じ、「PCから複合機への印刷指示」のGUIを取り上げることにします。
たまたま手近にあって使える富士ゼロックス製 ApeosPort-VI C3371 に印刷を指示してみます。最近の機種に対応した画面は以下の図に示すようなものになっているようです。左右の図は同じGUIの画面ですが、比較のために少し違った入力状態にしたものを示してあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/8387371f-fe2d-e706-a355-b9a01ea2cf94.png)

左図に対して右図では、「まとめて1枚」の機能で選択している値を変えています。「しない」以外の複数枚をまとめる処理をする選択を行っている場合、左図のオレンジの点線で囲んだ範囲の二つの機能のUI部分は、右の図の通り表示もされなくなります。
反対に、まとめて1枚の機能で「しない」を選択し、この機能を使用しない場合にだけ、左図のように残りの二つのUIが表示されます。
さらに、この左のチェックボックスですが、チェックされているときには右側の倍率値を変更することができます。
しかし一方、チェックされていない場合は、表示はされているがその値は100%に固定されており、ポインターをもっていって操作したり値をキーボードから入力したりしてみても一切受け付けず、触ることができない仕様になっています。
さて、このような状態を、ここではmask状態と呼ぶことにします。（n/aを使う方が多くの人には類推し易いかもしれませんが、ここでは原因結果グラフ法で同様の制約をmask制約と呼んでいることに倣うことにしました。）

このmask状態が存在する場合、先に述べた通り、どう扱うか悩んで進めなくなってしまっている現場がある一方で、その存在をすっかり忘れて突き進んでしまっている現場もあります。

順に説明していきますので、これを忘れたままで済ませてはいけないことが次第にお分かり頂けると思います。
実はこういうところにこそバグは潜んでいるものです。それなのに、忘れていること自体に最後まで気が付き難いケースが多々あるのです。失敗しないために手順として身につけておいた方が良いのではないかと思います。

本稿の以下の説明の手順ですが、まず問題の起きない一つの方法を先に示すことにします。
(そこまで読んで、それでやってみようと思われたらその後は読まなくても良いと思います。)

後半では、このようなmask状態を忘れたまま突き進むとどういった問題に出会うことになるか、を示します。
また他の方法として、HAYST法でのやり方を示し、簡単に比較してみます。

さて、それではまず上手く取り扱う方法の説明に入ります。

# 本稿推奨手順

ここで示す方法では、[PICT-PAPPを使って設計する場合の手順書](https://qiita.com/sho1884/items/db1662e75dbf84fccc94)の方にも示した通りなのですが、mask状態を通常の水準の1つとして加える方法を採ります。

組合せテストを設計するに際し、各因子について同値分割をやり直し、水準を選び直すことになると思います。その際に、必ずmask状態を一つの水準として加える必要について検討するようにします。(一般に異常系、無効値については一つずつでしか確認が難しいので組合せテストを設計するに際しては含めません。そうしたこともあってか、ここで示したようなmask状態は見落とされることが多いようです。もちろん複数のエラーが重なったときや、エラーからの回復処理途中でエラーが起きる場合の処理にバグが潜んでいることは多々あります。しかし、そのためのテストは別に設計します。)

上図に示し取り上げた事例で、注目すべき因子は以下の3つです。

- まとめて1枚
- 倍率を指定する
- 倍率

UIから抽出して同値分割を考えると、例えば水準として以下のようなものが挙がります。

- **まとめて1枚:** Nアップしない,	N最小の2アップ, N中央の9アップ, N最大の32アップ
- **倍率を指定する:** 指定しない, 指定する
- **倍率** 最小25パーセント, 等倍100パーセント, 最大400パーセント

ここで次のことを必ず検討することがポイントになります。
「これだけで十分か？」「mask状態を一つの水準として加える必要はないか？」

**「まとめて1枚」**のUIが表示できなくなったり、触れなくなったりするようなことはありません。従ってこの因子の水準にはmask状態は不要です。

**「倍率を指定する」**はどうでしょうか？
このUIは表示されなくなったり、触れなくなったりします。
「まとめて1枚」について、「Nアップしない」以外が選択されている時は非表示となります。従って、まずはこの因子の水準にはmask状態が必要であろうと考えられます。

**「倍率」**はどうでしょうか？
このUIも表示されなくなったり、触れなくなったりします。
「まとめて1枚」について、「Nアップしない」以外が選択されている時は非表示となります。従って、この因子も水準にはmask状態が必要であろうと考えられます。
さらに表示されている際にも、「倍率を指定する」がチェックされていない場合は、このUIは触れなくなります。100％を示したまま、触っても全く反応しなくなります。これは「100％しか入力できない」とも「mask状態」とも解釈できると思います。

さあ、それではここまでの情報を使って[ペアワイズツール](https://qiita.com/sho1884/items/db1662e75dbf84fccc94)でテストケースを自動生成してみましょう。

実務的な事例に近づけるために、ここまで注目してこなかった他の因子・水準も加えてみます。
ただし、ここでの説明を理解するために注目すべき因子とそうでないものを区別し易くするために、説明に直接関係しない他の因子名・水準名は意味のない名前とし、水準数も4に固定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/236cdf4f-f556-acd5-1e2a-62c5d0becc87.png)


因子・水準からPICT-PAPPで総当たり表を作成し、禁止されている組み合わせに×印をつけると、以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/43e70d48-8486-aadf-cf18-cc75e2f85413.png)


表示されなくなったり触れなくなったりするのだったらmask状態に(それ以外は全て禁止)、逆に表示され触れるのであれば少なくともmask状態は禁止です。

やることはこれだけです。

これでペアワイズツールでテストケースを自動生成した結果、またその総当たり表へのマッピングを見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/6f1cc1ff-562e-9520-1eed-ae50967d0973.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/98842ec6-01e6-f69f-a3c7-de0afbde5f0d.png)

このように、ちゃんと全てテスト可能なテストケースが、２因子の組合せ網羅率100％で生成されることが確認できます。
表示されなくなったり触れなくなったりする項目に対し、maskに設定しろ(バグが無ければシステムの側で勝手にそうなるはずなので、それを確認するだけでそこは何もしなくてよい)というテストケースが生成されるので、なにも困らずにテストできるわけです。
自動化している場合も同じで、maskが出てきたらそこは何もしない(あるいはUIが非表示になっていることなどを確認する)ようにプログラムしておけば良いということになります。
(さらに言えば、このシンボルはmaskである必要が無いので、例えば表示されなくなるものをmask、触れなくなるものをlockとするような運用ルールを決める自由もあります。)

難しいことはないですね。忘れずに「mask状態を一つの水準として加える必要性について検討」すれば良いわけです。

以上で上手くいくやり方の説明を終わります。

# mask状態を水準に加え忘れた場合、どういう事態が起こり得るか？

さて、ここから先は少し余計な話になります。

まず、「mask状態を一つの水準として加える」のを忘れて突き進んでしまったらどうなってしまうのでしょうか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/8ab288c3-155c-88f5-2b70-2ae7e552dc09.png)

その場合でも、ペアワイズツールで自動生成したテストケースは全てテスト可能なものが出てきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/d0a78c4d-cc53-26f3-db24-3ae4568607c4.png)

順調にテスト実施して「これで安心」等と思っていると、大きな落とし穴に落ちています。

自動生成されたテストケースをPICT-PAPPの機能を使って総当たり表にマップしてみると、以下の図のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/4ac56123-1a8f-e9b6-410b-5583347fc57b.png)

黄色に塗られた組み合わせは生成されたテストケースの中に一つも入っていません。
つまりそれらの組み合わせについては一度もテストされることがなく、見過ごされてしまっているのです。

「こんなの途中で間違いに気づくに決まっている」と思われたかもしれません。

しかし、本当に大丈夫ですか？
ここに示した事例の場合、極めて限定された関係しかありませんでしたし、当に注目している1か所に問題があるのですぐに気づくことができます。
でも現実のテスト対象システムには多数の制約関係があります。見過ごすことなくやって来れていますでしょうか？

困ってしまうのは、生成されるテストケースは全て実行可能なので、気が付かずに通り過ぎてしまう可能性があるということです。

幸い、ここで示した通り、PICT-PAPPの総当たり表のマッピング機能を使うと、この問題に気が付き易くはなります。
ぜひ試してみてください。

# HAYST法はこの問題にどう対処しているのか

さて、ここで示した課題、「あまりちゃんと説明されているものを見たことがない」と冒頭で述べましたが、皆無かというとそういうわけでもありません。

実はHAYST法においても独自の方法が考案されており、先に事例を引く際に参照した書籍『ソフトウェアテストHAYST法入門』の禁則の定義方法の説明の中で、説明はされています。
ただその説明は、一般の禁則マトリクスの入力手順の各箇所で、NULLについても操作が必要となった場合に補足説明を加える、という形で散在して記述されており、考え方を理解するのには少しわかり難い表現となってしまっています。

以下で関係する内容を書籍から拾ってみます。
本稿が上手くいく方法として示したものとよく似ているのですが、違う部分もあります。混乱しないように違いに注目しながら説明していきます。

本稿で導入したmask水準と同様に、HAYST法では**NULL水準(NULL level)**が導入されています。
NULL水準は、同書籍の用語集の中で**「何も設定しない水準」と定義**されています。
（maskは本稿で紹介した上手くいく方法の中で導入したもの、NULLはHAYST法で導入されているものなので、区別がつくと思います。区別がつくであろう限りにおいて、どちらの手法においての話であるかの記述を以降は省略します。）

NULL水準はmask水準と違って、通常の水準の一つではなく、特別扱いになっています。
NULLは特殊なシンボルで、水準名として使用することが禁止されています。ツールにおいても、自分で入力することも削除することもできません。
NULLは禁則関係を禁則マトリクスで定義しようとしたときに、条件側に設定した因子の全てに対して自動的に必ず一つの水準として現れます。（必要が無い場合も削除することはできません。）
同書籍の中で「条件因子にのみ存在するNULLは，条件因子がテスト時に存在していないものとして制約因子を取り扱うことができるかどうかを表している.」と説明されています。

一方、被制約因子の側や総当たり表の表示の中では、このNULLは隠されていて、決して表示されることはありません。

ツールが生成する結果(組み合わせテストケース)の中にはNULLは当然現れることがあります。
テストケースの中に出現するNULLは、その値が設定できないことを意味します。これはmaskの場合も全く同じでした。
テストを実施する上では、設定できないわけですから、「何もしなくともそれで構わない」と指示されたと思えば良いわけです。

さて、ここで改めてmaskとNULLの違いを確認してみましょう。
双方とも意味としてはほとんど変わらないものとして導入されています。ただ、maskは(人が特別な意味を割り付けているだけで)通常の水準の一つとして扱うことにしているのに対し、NULLは特別扱いにされています。
maskに比して注目すべきその特別さは、次のようなことになると思います。

- 各因子には全て漏れなくNULL水準が内部的に設定されている。(削除できない)
- NULLについては、因子・水準の入力インタフェース、被制約因子、総当たり表においては表示されない。

NULLについて、上記のような特別な扱いをすることにした理由は何だったのでしょうか？

おそらく、次のような親切心からでしょう。

- このような意味合いの水準は入れ忘れられ易いので、予め必ず入れるようにした。
- しかし、いつもNULLが出てくるととても鬱陶しいので、必要が無い限り、その存在を隠すことにした。

しかし先に示したNULL表示の方針では、残念ながら本当の意味で「必要が無い限り」ということを実現できていません。
必要なのに表示されなかったり、必要ないのに表示されたりしてしまうので、今考えると残念ながらこの親切心はあまり良い結果になっていないように思います。

- ツールがNULLを予め必ず入れることと、多くの場合隠されることで、これを忘れることを奨励するかのような結果になってしまっている。
- 実際に水準としてNULLが無い因子も多い。また水準として実際にあるとしても、そこに禁則を設定してもしなくても結果が変わらないケースは多いので、無視する癖がつく。
- しかし完全に忘れてしまっていては全て自動的に上手くいくわけではないので、必要な時にはNULLに対する禁則をユーザが入力しなければならない。

その結果、さらに何が起こっているかといえば、以下のような事態です。

本来、テスト設計者は「対象システムの仕様として、どこが禁則になっているかを正確に把握し、それを明確に定義すること」に注力すべきです。maskについてはそうできていました。
ところが、NULLは結果に不都合が無い限り忘れていて良いことにしてしまったため、反対にどういう場合に結果に不都合が出るのか、ということに注意を図りながら設定しなくてはならないことになってしまいました。
「どういう場合に結果に不都合が出るのか」というふうに見ていくと、いくつかの場合があるので説明が複雑になってきます。特に初心者向けに場合分けしてそれぞれに必要な説明だけをすると、その時々で違った説明になっているように感じられ、訳が分からなくなってしまうようです。

よくわからなくなってしまって放置すると、maskを忘れたときに起きたのとまったく同じ問題が結局起こります。
それで実際にテストできないケースが出てきて困って気が付くならまだ良いですが、反対に出てきていない組み合わせに気付かずに通り過ぎてしまう危険性もあるわけです。

まあ、この辺り、様々なレベルのユーザを想定しなければならない商品設計の難しさが表れてしまったように思います。よくわかっている人には大差がなく、どちらの方法が良いかは好みの問題かもしれません。

# 最後に書籍の事例で双方の比較を試みようとしましたが、それは不毛なことであると気付くことに

さて、最後に先程から取り上げている書籍『ソフトウェアテストHAYST法入門』の中で使われている事例で、NULLに対する禁則設定と、maskの設定との比較を示して終わりにしたいと考えました。

しかし、結局のところどちらもテスト対象の仕様を忠実に(NULLやmaskを含めて禁則を漏れなく)入力すれば、遜色のない期待結果が得られます。また、比較のために条件を揃えようとすると、それは入力を揃えることになってしまい、つまりは何も変わらなくなってしまいます。

ただ、HAYST法ツールの方が、NULLの設定をサボっても問題のない結果が得られることが**多い**、ということなのです。これは便利な機能です。
しかし一方、必ず良い結果になるとは限らないので、正しくNULLの設定をしてやらないと、実際にはテストできないものが出てきたり、ヌケモレのある結果が出てくることがあります。（テスト実施の時になって、あるテストケースが実際にはテストできないと気付いたからといって、それを飛ばしてはいけません。入力できない部分以外の因子の水準の組み合わせは、そのテストケースにしか含まれていない可能性が高いからです。）

結局、サボってよい箇所とそうでない箇所を正しく判断できる知識が必要で、また漏れなく対応するためには全箇所についてサボってよいかどうかを少なくとも検討はするべき、ということになります。

というわけなので、本稿の最後は書籍に載っている事例について、HAYST法ツールでNULLの設定をサボった場合に何が起こるかを以下に示して終わりたいと思います。

書籍の取り上げている事例の画面と、その場合に設定すべき禁則として示されているのは以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/9d9a510b-ac63-3d11-9542-ce92e7976478.png)

HAYST法ツールに実際に入力してやってみた結果が以下の画面です。確かに問題のない結果が得られています。

![多層化失敗事例 書籍通り100%.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/fa60db2f-882f-4e2c-404d-f634baaa9f15.png)

「NULLに対する禁則」を与え忘れたとすると、以下のようになります。先の図と比較すると、実際には入力できないような問題のある組み合わせが生成されてしまっていることが見て取れると思います。

![多層化失敗事例 書籍通り100% null無し.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/96c8c3af-eb45-7640-beb0-e34b033e7352.png)

ここで先に示したmaskを忘れた場合と比較したいところですが、その前に別の違いが目につくことになってしまいます。気付かれた方も多いと思いますが、書籍に示されている禁則関係には「まとめて１枚」と「倍率」の間の禁則関係について入力されていません。先のNULLを正しく設定した図の方ではその部分はオレンジ色に塗られている部分になります。オレンジ色の部分は間接禁則と呼ばれており、HAYST法ツールのマニュアルには「複数の禁則間の関係により発生する間接的な禁則」と説明されています。
（余談ですが、PICT-PAPPの方では間接禁則は黄色で表示されます。また、全ての黄色で示された組み合わせが間接禁則になっていることを確認するAlloyのソースを自動生成し、Alloy Analyzerを自動起動して確認することができます。）

私は個人的には間接禁則を好みません。それは間接禁則になるであろうことを分かっていてそうしたのか、見落としがあったのか、という区別が後からわからなくなるからです。分かっているのであれば初めからちゃんと入力し、それでも間接禁則が出てきてしまったら見落としがあったということになるので、全体をもう一度チェックし直します。(HAYST法では多層化と呼んでいる禁則関係で、層が深くなった場合にも、「必ずあなたは全部そうするのか」と聞かれると、ちょっと迷うところも確かにあります。しかし少なくともこの事例の場合、二つのUIが同じように消えたり現れたりするわけですし、意味としても機能としても倍率を指定できなくなるわけですから、直接禁則にする方が多数派なのではないかと想像します。根拠があるわけではありませんが、、、)
まあ、この辺りの機能の使い方も、ちゃんとわかっている人であれば好みの問題、ということで良いように思います。

せっかくここまで入力してやってみたので、「maskを使う方法と比較するために条件を揃えよう」と考え、「まとめて１枚」と「倍率」の間の禁則関係についてもしっかりと入力すると、以下の図ようになります。ちょうど間接禁則になっていた部分を直接禁則設定したことになります。

![多層化失敗事例 間接禁則を直接禁則に.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/8806f6cd-dbdd-7b03-616d-72d01c13423d.png)

ところでこの場合は、NULLの設定をサボっても（忘れても）、以下の通り何ら問題は起こらなくなってしまいます。（問題が消滅してしまう事例、あるいはもともと問題の起こらない事例であった、ということになります。）

![多層化失敗事例 間接禁則を直接禁則に null無し.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246520/c0c57597-04bc-7952-81f3-0d60e7aa6227.png)

# 最後に

前半の本稿推奨手順を気に入って頂けたならば幸いです。是非、実務で活用してみてください。

後半についてはHAYST法をご存知の方以外にはあまり面白くなかったかもしれません。
結局、注意すべき大事なことは次のことになると思います。（こういう風にまとめると、なんか当たり前で凡庸ですが、、、）

- 「ツールにすべてお任せは危ない。」
- 「そのツールの特性を良く理解したうえで、使いこなしましょう。」
- 「何より、テスト対象の仕様をヌケモレなく調べて、ちゃんと表現（ツールに入力）しましょう。」