【3分で読める】誤った説明をしてないのにバグを見逃す話【追加解説付き】

## はじめに：どうコードを書きますか？
突然ですがアンケートです。
「入力された年齢を元に、未成年に酒類を販売しないようにしてください」
と言われた際、皆さんはどのように条件分岐を記載しますか？

1. `if (X<20) {NG} else {OK}`
2. `if (X≦19) {NG} else {OK}`
3. `if (X≧20) {OK} else {NG}`
4. `if (X>19) {OK} else {NG}`

（あくまで例なので細かい記法等は無視してください）

上記1～4は範例になりますので、他にも色んな手があると思います。
また、前後のコードの文脈や、他機能との整合性から、適切なロジックは変わるでしょう。
しかし、どれも要望を満たしていますので、すべて問題なく、正しいものです。

## 次に：どう説明しますか？
それでは、仕様書やQA、テスト担当者に伝えるとき、何と言いますか？
おそらくは、上記コードどれにしたか関係なく、
「20歳未満には酒類を販売しないようにしました」
「20歳未満ならエラーを返します」
と言うのではないでしょうか。

何故ならば、世間一般的に「20歳未満の飲酒は禁止されています」と定型文のように、
『20歳未満』という文字列に馴染んでいるからでしょう。
そして、コードがどれであれ、「20未満ならNGを返すことに変わりない」です。
なので、コードがどれであっても説明として誤りはありません。

## 最後に：バグを見逃す理由
基本的なテスト技法に「境界値分析」というものがあります。
端的に説明しますと「ある数値を境に動作が変わる部分を重点的に確認する」ものです。

「20歳未満ならエラーを返します」と説明した場合、
その説明をそのまま受け取ると、境界値を「20」としてテストが実行されます。
しかし、アンケートにあったコードには、境界値を「19」で書かれたものがあります。
つまり、誤ったテストを実行してしまい、バグを見逃す可能性が発生する、ということです。
より詳細な解説は最後に載せています。

## まとめ
どこにも誤りは無いのに、誤ったテストを実行してしまう話でした。
今回は分かりやすく値を例として話しましたが、他の機能周りでも十分発生し得るかと思います。

こういった事例は、説明や仕様からテストを設計/実行するQAチームには致命的な内容です。
「これくらいコードを読めばいいじゃないか」とは非常に思いますが、
「コードを読む」ということは、書くことに並ぶ特殊技能であり、
読めるQAは多くありません。
また、組織によっては、QAチームにコードの参照権限がそもそもないパターンもあります。

どれくらい説明すればいいのか、の塩梅は難しい所です。
QAチームの習熟度や、コードを読める人が居るのか等、最適解は組織やタイミングによって変わります。
　
　
　

ここまでが3分
以降はおまけ解説
　
 　
 
## 詳細な解説
境界値分析は一般的に、
「動作が変更される最大値と最小値で実施する」
というものです。

一旦、各コードを自然言語化しましょう
1. if (X<20) {NG}　→　20未満ならNG
2. if (X≦19) {NG}　→　19以下ならNG
3. if (X≧20) {OK}　→　20以上ならOK
4. if (X>19) {OK}　→　19より大きいならOK

つまり、今回の例であればどの説明をされても「19と20でテストする」となります。
そうです、ある程度テスト技法に明るい人であれば、
「どのコードでもどの説明でも同じテストじゃないか、嘘つき！」
になるところかと思います。

実は、「動作が変更される最大値と最小値で実施する」境界値分析は、完璧でありません。
これは2ポイント境界値分析という方式です。

境界値分析が生まれた背景は「不等号に誤りが多いから、そこを重点的にテストしよう」です。

以下に例として、「20歳以上ならOKのコードにしよう」とした時、
不等号を誤ったケースの一覧です。
正しいものは下から2番目の太字の行です。
| if(hoge){OK} |  | 19歳に販売 | 20歳に販売 |
|:-:|:-:|:-:|:-:|
| X<20 | 20未満 | OK | NG |
| X≦20 | 20以下 | OK | OK |
| **X≧20** | **20以上** | **NG** | **OK** |
| X>20 | 20より大きい | NG | NG |

見ての通り、誤った不等号を使用しているものは2ポイントの境界値分析で問題を検出できます。
ですが、以下の表は誤って等号を用いた場合です。

| if(hoge){OK} |  | 19歳に販売 | 20歳に販売 |
|:-:|:-:|:-:|:-:|
| X≧20 | 20以上 | NG | OK |
| X=20 | 20の時 | NG | OK |

このように、テスト結果は同じであり、誤りを見抜けません。
つまり、等号を記載されていた場合には2ポイントの境界値分析では誤りを検出できません。

それを解決する手段として3ポイント式の境界値分析があります。

3ポイント式の境界値分析では、定められた境界値とその前後の値をテスト対象とします。
つまり、「20歳以上はOK」と言われたら対象は「19, 20, 21」です。
| if(hoge){OK} |  | 19歳に販売 | 20歳に販売 | 21歳に販売 |
|:-:|:-:|:-:|:-:|:-:|
| X≧20 | 20以上 | NG | OK | OK |
| X=20 | 20の時 | NG | OK | NG |

こうして、3ポイントの境界値分析であれば、誤って等号を使用された場合も検出できます。


では、3ポイントの境界値分析の対象となるものを各コードに当てはめると、
1. if (X<20) {NG}　→　19, 20, 21
2. if (X≦19) {NG}　→　18, 19, 20
3. if (X≧20) {OK}　→　19, 20, 21
4. if (X>19) {OK}　→　18, 19, 20

となります。
境界値に19を使っているコードの場合に、誤った値でテストを実行する結果となります。

実際に、コード上の境界値は19である場合に、
テスト担当者が「20歳未満と言われたから境界値は20だな」としてテストを実行すると、
以下のようになります。
| if(hoge){NG} |  | 19歳に販売 | 20歳に販売 | 21歳に販売 |
|:-:|:-:|:-:|:-:|:-:|
| X≦19 | 19以下 | NG | OK | OK |
| X=19 | 19の時 | NG | OK | OK | 

テスト結果からは、誤って等号を書いたケースを検出できないことがわかると思います。

「不等号ミスならともかく等号は流石に気付く」
「ちょっとでも条件違うとそんなことは起こらない」
「そんな事態になる確率がそもそも低い」
と思われるかもしれません。正直私もそう思います。
しかし、技法にまでなってるということは、それだけ多発した歴史がある裏付けです。


## QA向け追記
(QAがQiitaを見ているとは思いませんが……)
JSTQBの2023年版シラバスには、境界値分析はブラックボックスだと書いてありますが、誤りだと感じています。
何故ならば、ここまで説明した通り、仕様や説明と実際のコードには乖離がある可能性があります。
というか、動作的にはすべて同じ結果なので、そこまで細かく説明を求めるのは酷です。
仮に全部解説されたとして、それはコードを読めないと理解もできないので、結局読めるようになるのが最も良いです。
つまり、正しく3ポイント境界値分析を行うには、コードを確認するホワイトボックスでなければ対応できません。

もしもブラックボックスでやるのであれば、これは私の考案した方法ですが、
**4ポイント境界値分析**を実施する方が良いです。
JSTQBでは誤った認識と記載されていますが、
「19と20で動作が切り替わるならば境界は19と20の間」という発想です。

つまり4ポイントの境界値は「18, 19, 20, 21」の4つです。
19と20のどっちで3ポイントをやればいいかわからんなら両方実施、ということです。
19の3ポイントの境界値「18, 19, 20」と20の境界値「19, 20, 21」を合成してます。
コードを読まないならどっちの数字が境界値として正しいかわからんのなら、
ガチでやるなら両方ともやれよ、という話ですね。