ブラックボックステストは内部構造を想定する

[ソフトウェアテストの小ネタAdvent Calendar2021](https://qiita.com/advent-calendar/2021/software-testing-koneta) 5日目の記事です。

前日(4日目)は@teamomusobaさんの[QA大喜利](https://teamomusoba.hatenablog.com/entry/2021/12/04/191054)でした。大喜利は自分で考えると難しいですよね。明日(6日目)は@TaoBabubbさんの[もしも寿限無の和尚さんがテスターだったら](https://note.com/mjtns/n/n77c271333750)です。

# はじめに

ブラックボックステスト技法とは「テスト対象の入力と出力に着目し、その内部構造は参照しない」テスト技法を指します[^jstqbfl]。内部構造を参照しない、とありますが、実際にブラックボックスなテストを行うときは内部構造を想定している、という話を書きます。

[^jstqbfl]: http://jstqb.jp/dl/JSTQB-SyllabusFoundation_Version2018.J03.pdf

## 結論

* あらゆる不具合を想定したブラックボックステストはあらゆる入力を試す必要がある。しかし、現実的ではない。
* ブラックボックステストはテストエンジニアによる内部構造の想定が入る
* 内部構造を想定するときに、有識者と内部構造を共有することが有用と考えられる
* 内部構造を想定するときに、内部構造を作る人のことを想定することが有用と考えられる

# ブラックボックステストの例

以下の関数のテストをします。

```c:sample.h
// 絶対値を取る関数
// signed charは[-128, 127]
// ただし、-128 = myabs(-128)とする
signed char myabs(signed char value); 
```

##テストケース例

例えば、以下のようなテストケースが挙がったとします。

|入力|出力|説明
|-|-|-|
|127|127|正の整数上限値|
|95|95|正の整数代表値|
|1|1|正の整数下限値|
|0|0|正負に当たらない整数|
|-1|1|負の整数上限値|
|-63|63|負の整数代表値|
|-127|127|負の整数下限値|
|-128|-128|特殊ケース|

##実装例

では、実装が以下だったとします。

```c:sample_ifelse.c
signed char myabs(signed char value)
{
    if (value == -128) { return -128; }
    else if (value == -127) { return 127; }
    else if (value == -126) { return 126; }
    ...
    else if (value == -101) { return 101; }
    else if (value == -100) { return 101; }  // 実装ミス
    else if (value == -99) { return 99; }
    ...
    else if (value == 126) { return 126; }
    else { return 127; }
}
```

この場合、先に挙げた[テストケース例]では実装ミスの不具合を検出することができません。このような実装ミスをテストで検出するには、myabsのすべての入力を試す必要があります。

ブラックボックステストでは内部構造を参照しないため、どのような実装をされるか分かりません。つまり、ブラックボックステストではすべての入力を試す必要があるんですね。これで安全！ヨシ！

…なわけは無いですよね。

#ブラックボックステストはテストエンジニアによる内部構造の想定が入る

テストの7原則[^jstqbfl]でも「全数テストは不可能」とあるとおり、ブラックボックステストだからといってあらゆる不具合に想定するために全数テストを選択することは現実的ではありません。

実際は以下のようなソースコードを想定して、[テストケース例]で挙げたようなテストを行うことが多いと思います。

```c:sample.c
signed char myabs(signed char value)
{
    return 0 <= value ? value : -1 * value;
}
```

このように、ブラックボックステストでは「内部構造を参照しない」としながらも、テストエンジニアによる「内部構造の想定」がテストケースに反映されます。すべての不具合をテストで補足することは不可能なので、ブラックボックステストでは「どの不具合を補足するか」を明瞭にしておく必要があります。

書籍『ソフトウェア・テストの技法 第2版』でも「徹底的なテストは、不可能で問題外であるから、有限の数のテストで発見されるエラーを最大にすることによってテストに対する投資の成果を最大にすることを目的とすべきである」と述べられています。

# 内部構造を想定するときに、有識者と内部構造を共有することが有用と考えられる

ブラックボックステストは内部構造を参照しませんが、内部構造に対する理解によって大きく生成するテストケースが変わると言えます。例えば、「sample.c」のような内部構造を知っていれば、テストケースは少なくて済みます。「sample_ifelse.c」のような内部構造を知っていれば、（大変だと知りつつも）全数テストを行う必要があります。

内部構造を正確に把握することはとても難しいですが、有識者と内部構造の概要を共有するだけでもテストケースを考える材料になるといえます。想定だけでなく確度の高い情報を使うことで、効率的にテストケースを考えることができます。いわゆる「グレーボックステスト[^gray]」になります。

[^gray]: https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AC%E3%83%BC%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%83%86%E3%82%B9%E3%83%88

# 内部構造を想定するときに、内部構造を作る人のことを想定することが有用と考えられる

ソフトウェアの内部構造を想定するのも重要ですが、そのソフトウェアを作る人を理解することが有用に働く場合もあります。実装者（もしくはチーム）により、ソフトウェアの作り方に癖が生じることがあります。その癖は多少なり、ソフトウェアの不具合の傾向に影響を与えると考えられます。例えば、過去に「sample_ifelse.c」のようなプロダクトコードを書いたことのあるチームのソフトウェアをテストする場合、（大変と知りつつも）普段よりもテストケースを多めにして不具合を検出しやすくする戦術が考えられます。このように、ソフトウェアだけでなく、それを作る人にも関心を持つとより良いテストケースが作りやすくなる場合があります。

# おわりに

ブラックボックステストが「内部構造を参照しない」という説明が一般的ですが、実際にブラックボックステストを考えるときは内部構造を意識する、ということを例と合わせて説明しました。

内部構造を有識者と共有したり、作り手の癖を考えることで、不具合検出率の高いブラックボックステストを作りやすくなると思います。