Pythonで〇×ゲームのAIを一から作成する　その24　勝敗判定のテストの手法

# 目次と前回の記事

https://qiita.com/ysgeso/items/2381dd4e3283cbed49a0

https://qiita.com/ysgeso/items/1fdd19037bbf64082a42

# 実装の進捗状況と前回までのおさらい

## 〇×ゲームの仕様と進捗状況

1. `正方形で区切られた 3 x 3 の 2 次元のゲーム盤上でゲームを行う`
2. `ゲーム開始時には、ゲーム盤のすべてのマスは空になっている`
3. `2 人のプレイヤーが遊ぶゲームであり、一人は 〇 を、もう一人は × のマークを受け持つ`
4. `2 人のプレイヤーは、交互に空いている好きなマスに自分のマークを 1 つ置く`
5. `先手は 〇 のプレイヤーである`
6. __プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する__
7. __すべてのマスが埋まった時にゲームの決着がついていない場合は引き分けとする__

仕様の進捗状況は、以下のように表記します。

* 実装が完了した部分を `背景が灰色の長方形` で記述する
* 実装の一部が完了した部分を、__太字__ で記述する

## これまでに作成したモジュール

以下のリンクから、これまでに作成したモジュールを見ることができます。

https://github.com/ysgeso/marubatsu/blob/master/024/marubatsu.py

## 前回までのおさらい

前回の記事で、仕様 6、7 のゲームの勝敗判定を行う `judge` メソッドを実装しました。[前回の記事の最後](https://qiita.com/ysgeso/items/1fdd19037bbf64082a42#judge-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E3%83%90%E3%82%B0%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6) で説明したように、`judge` メソッドにはいくつかバグがあります。今回の記事ではそれらのバグの見つけるためのテストの手法について説明します。

# テストとは何か？

これまでの記事でも、関数やメソッドを実装したり、修正したりするたびに、その関数が __正しく実装できているかどうかを確認__ してきました。このような、実装したプログラムが正しく動作するかどうかを確認する作業のことを、__テスト__（日本語では __検査__） と呼びます。

## テストを行う理由とテストの規模

プログラムに限った話ではありませんが、人間は __ミスをする生物__ なので、どんな作業を行う場合でも、__ミスが起きる可能性__ は __ゼロにはなりません__。そのため、何らかの作業を行う際には、一般的にその __作業の重要性__ に応じた __テスト（確認）の作業__ を行います。

例えば、家を長い間留守にする場合は、出かける際に留守の間に泥棒に入られないように、戸締りの __確認を行います__ が、その際に行う __確認の作業__ は、__家の財産に応じ__ たものになるはずです。家の中に __盗まれるようなもの__ が __あまりない__ 場合は __簡単な確認作業__ で済ます人が多いと思いますが、__大事な財産__ が家にある場合は __念入りな確認作業__ を行う人が多いでしょう。

## テストの規模と精度

テストを行ったとしても、テストの目的が __必ず達成されるとは限りません__。例えば、どれだけ念入りに時間をかけて戸締りを確認したとしても、留守の間に __絶対に家に泥棒が入らない__ という __保証は得られません__。

また、テストは人間が行うものなので、__テストそのもの__ にも、__ミスが起きる__ ことがあります。例えば、確認したつもりが、__うっかり__ 窓の鍵を __確認し忘れる__ ような場合です。

テストに時間と労力をかければ __かけるほど__ テストの __精度は高く__ なりますが、テストに費やした時間や労力と、テストの精度は __比例しません__。__一般的__ には、下記のグラフのように、__最初__ は急激に __テストの精度が上昇__ しますが、ある程度以上の時間や労力からは、__精度が__ あまり __上昇しなくなります__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/1dc7cdfb-695d-d14d-c951-02de8ddd9315.png" width="200">

理想的には、テストに無限の時間と労力をかければ、テストの精度を 100% に __近づけていく__ ことは可能ですが、__現実的__ には、時間や労力は __無限に費やせるわけはありません__。そこで、何らかのテストを行う際には、__求めるテストの精度__ と、テストに費やすことが出来る __時間と労力__ の __バランスを考えてテストを行う__ ことになります。

## テストを行う範囲

一般的に、テストを行う __範囲が広いほど__ 、テストにかかる __時間と労力が増大__ し、テストの __精度が低くなる__ 傾向があります。例えば、大邸宅の戸締りをすべて確認するテストと、小さな部屋の戸締りだけを確認する場合の事を考えてみて下さい。__小さな部屋__ であれば、__確認__ しなければならないことが __少ない__ ので、一般的に __少ない労力__ で、__高い精度__ の戸締りを行うことが出来るはずです。

そこで、テストを行う __範囲を小さく分けて__、その __範囲ごと__ に __複数の小さなテスト__ を行い、その結果を __うまくまとめる__ ことで、__全体のテストを行う__ ということが良く行われます。また、大きな範囲を一度でテストするよりも、そのような方法のほうが __効率よく__ テストを __行うことが出来る__ 場合が多いことが __わかっています__。

例えば、__学力テスト__ では、一度のテストですべての学力を測るのではなく、国語、数学、英語のように、__細かい分野に分けて__ テストを行います。また、そうやって測定した個々のテストの結果を __総合的に判断__ したものを、その人の __学力__ と __みなします__。

:::note
このような、「そのままでは解決できない大きな問題を小さな問題に分割し、そのすべてを解決することで、最終的に最初の問題全体を解決する」という手法の事を、__分割統治法__ と呼びます。
:::

https://ja.wikipedia.org/wiki/%E5%88%86%E5%89%B2%E7%B5%B1%E6%B2%BB%E6%B3%95

プログラムの場合でも同様で、これまでに行ってきたように、プログラム全体を一度にテストするのではなく、最初は関数などの __小さな単位ごと__ にテストを行うのが __一般的__ です。関数やメソッドなどの __小さな単位__ に対して行うテストの事を、__単体テスト__ と呼びます。単体テストは、関数やメソッドを __実装__ したり、その内容を __修正した際__ に行います。

本記事では、以後は単体テストのことを、単に __テスト__ と表記します。また、__関数やメソッドに対するテスト__ という表記は長いので、以降は単に __関数のテスト__ と表記します。

:::note
プログラムのテストには、他にも結合テストや統合テストなど、__様々な種類__ があります。結合テストや統合テストは、単体テストでは発見できないようなバグを発見するためのテストで、複数のモジュールを __組み合わせた__ ような __大規模なプログラムを実装する際__ に行われます。

結合テストや統合テストなどは、単体テストに比べて時間と労力が必要となるようなテストです。本記事で取り扱うような個人が作成する __小規模なプログラム__ では、それらのテストに費やす __時間と労力__ が、得られるメリットに __釣り合わない__ 場合などではあまり行われません。本記事でもそれらのテストは行いません。

他にも様々なテストの手法がありますが、その種類は多岐にわたり、すべてを説明しようとすると一冊の本になるくらいの分量になってしまいます。本記事では数あるテストの中で、__小規模なプログラム__ で良く使われる __手法の一つ__ を紹介します。他のテストの手法に関して興味がある方は調べてみると良いでしょう。
:::

## プログラムのテストを行う目的

プログラムで何らかの処理を実装した際に、__バグが発生する可能性__ は __常に存在__ します。プログラミングの技術がどんなに上達したとしても、その可能性を __ゼロにすることはできません__。例えば、`1` という数字を間違って `2` と入力しただけで、バグは発生します。

ある程度以上の規模の __プログラムを実装__ した際に、一度で __バグが存在しない__ プログラムを __実装する__ のは __現実的には不可能__ だと考えたほうが良いでしょう。従って、プログラムには __バグがつきもの__ で、ほぼ確実に __バグを修正する作業__ が __必要__ になります。

プログラミングは、__バグとの闘い__ だと言っても過言ではありません。そのため、バグを __早期に発見する__ ような __工夫__ や、バグが発生した際に __バグの原因を発見しやすくする__ ような __工夫__ を行うことが __非常に重要__ です。__テスト__ はそのような __工夫の一つ__ です。

プログラムのテストは、大きく分けて以下の 2 つの目的で行います。

* 実装したプログラムが、仕様通りに __正しく動作するかどうかを確認__ する。バグを __早期に発見し__、発見できれば __その場で修正する__ ことが出来るようにする
* テストを行った部分に __バグが存在__ する __可能性を低くする__。__将来バグが発生__ した際に、バグが存在する __可能性が高い場所__ を絞り込んで、__効率よく見つける__ ことが出来るようにする

__関数のテスト__ は、胃カメラなどの、__臓器の検診__ に似ている所があります。定期的に __臓器の検診を行っておく__ ことで、臓器に病気がないかどうかを __早期に発見__ することが出来ます。これは、関数の __修正、実装__ を行った際に __テストを行う__ ことに相当します。

また、__病気になった場合__ に、病気の原因を、検診で __正常だとみなされた__ 臓器を __除いて調べる__ ことで、__効率よく__ 見つけることが出来ます。これは、テストを __行っていない部分__ に __絞り込んで__ バグを __効率良く__ 見つけることに相当します。

前者の目的の意味と重要性は直観的に理解できると思います。後者の目的も __非常に重要__ ですが、初心者にはわかりにくいかもしれませんので詳しく説明します。

:::note
他にも「ソフトウェアの開発コストの削減」、「ソフトウェアのパフォーマンスの向上」などのメリットがあります。下記に関連するページを紹介します。
:::

https://www.ibm.com/jp-ja/topics/software-testing

### テストによるバグの絞り込み

プログラムのテストは、バグを 100% 無くすために行うものだと __思っている人がいるかもしれません__ が、先程説明したように、__どんなに時間をかけた__ テストでも、バグを __100% 発見できるとは限りません__。テストによって、バグを完全に無くすことができるという考えを持っている人は __その考えを捨てたほうが良いと思います__。

テストがバグを 100 % 発見できないのであれば、テストを行う意味がないのではと思う人がいるかもしれませんが、先程のグラフのように、__簡単なテスト__ であっても、__ある程度の精度を得る__ ことができます。そのため、簡単なテストであっても、__事前にテストを行っておく__ ことで、後からバグが発生した場合に、__バグが存在する可能性が高い場所__ を __絞り込む__ ことで __効率よく探す__ ことが出来るようになります。

テストを行ったということは、__行ったテストで発見できるようなバグ__ が起きる __可能性が低い__ ということを意味します。そのため、バグが発生した際に、それまでに行ったテストで __発見できるようなバグの可能性を排除__ して __絞り込んで探す__ ことが出来ます。

現実世界で __問題が起きた時の対処法__ のことを考えてみて下さい。発生した問題に対する __原因の可能性が複数ある__ 場合は、原因の __可能性が高いものから__ チェックを行うのが一般的ではないでしょうか？例えば、テレビのリモコンが動作しなくなった場合に、真っ先に調べるのは __電池の残量__ ではないでしょうか？リモコンを高い所から落下させたような、強い衝撃を与えた場合でもない限り、リモコンの中の特定の回路が故障したと真っ先に思い浮かべる人は __ほとんどいない__ でしょう。

上記のリモコンの例では、問題が発生してから原因を調べましたが、__事前に__ 電池の残量をチェックしてあれば、電池をチェックするという __手間を省いて__、__次に__ 故障の __可能性の高い原因__ をチェックすることができます。プログラムのテストも同様で、__あらかじめ__ 発生する __可能性が高そうな__ バグがないかどうかを __事前にテストしておくこと__ で、バグが発生した場所を __効率よく探す__ ことが __出来るようになります__。

このように、__プログラム__ で行うテストと、__現実の世界__ で行うテスト（検査）は、その __性質や目的__ は多くの点で __良く似ています__。

なお、テストはすべての関数に対して必ず行う必要がある __わけではありません__。テストをするまでもないような __単純な処理__ しか行わないような関数であれば、__テストを省略する__ ことは良くあります。一般的に、テストにどの程度の労力をかけるかは、関数で行われる __処理の複雑さ__ によって変わります。特に、実装した関数が正しく動作するかどうかが __自信が持てないような場合__ は、ある程度の __労力をかけるべき__ でしょう。複雑な関数のテストを省略した結果、__後からバグが見つかった場合__ は、そのバグを修正するために __非常に大きな手間がかかる__ 事が良くあります。__急がば回れ__ という格言があるように、__簡単なテストでも構わない__ ので __複雑な関数__ に対してはテストを __行ったほうが良い__ でしょう。

:::note warn
テストに関するまとめは以下のようになります

* テストを行うことで、__ある程度の精度__ で __バグを発見する__ ことができる
* __時間と労力をかけるほど__ テストの __精度は上がる__ が、__100 % にはならない__
* テストによってすべてのバグを発見することができると __考えるべきではない__
* テストを行うことで、__後からバグが発生__ した場合に、バグが発生した __可能性の高い場所__ を __絞り込んで__、__効率よく探す__ ことが出来るようになる
* テストにかける __時間と労力__ は、__求める__ テストの __精度__ と __費やすことが出来る時間と労力__ を __考慮した上で決める__
:::

:::note
プログラミングが上達すればするほど、プログラムのミスが少なくなるので、テストを行う必要が少なくなくなると思っている人がいるかもしれませんが、それは __大きな間違い__ です。プログラミングが上達すればするほど、テストの __重要性が身に染みてわかる__ ようになるからです。

熟練者は __どのような場合__ にバグが __発生しやすいか__ がわかるようになるので、テストが __必要な場合__ に __効率よく__ テストを行うことができるようになります。そのような意味では、テストに費やす時間が減るというのは正しいかもしれません。

例えば、運転免許を取り立ての人は、__経験が足りない__ ので運転中にどのような危険があるのかが __よくわかりません__。そのため、__ありとあらゆる場合で注意を向ける__ 人もいれば、人が飛び出してくる可能性が高い __危険な場面での注意を怠る__ 人もいるでしょう。一方、ベテランの運転手は、危険が潜んでいる可能性の高い場所が __経験からわかっている__ ため、__効率よく注意を向けながら__ 運転を行うことが __出来るようになります__。また、その際に、__危険が潜んでる可能性が高い__ 場所に対して __注意を怠る__ ようなことは __しなくなるでしょう__。
:::

# 関数のテストの実施方法

前回の記事で実装した、勝敗判定を行う `judge` メソッド __より前__ に実装した関数やメソッドは、行う処理が __単純なもの__ ばかりだったので、__単純なテスト__ で済ましていました。一方、 `judge` メソッドはこれまでに実装してきた関数と比べて __複雑な処理を行っている__ ので、そのテストで行う作業も少々 __複雑になります__。

関数のテストは、__様々なデータ__ に対して関数を何度も呼び出し、__それぞれのデータ__ で __正しい処理__ が __行われるかどうかを確認__ します。ここでいうデータとは、関数の場合は関数に渡す __実引数__、メソッドの場合は __インスタンスの属性__ や __実引数__ などが挙げられます。

:::note
先程のノートで説明したように、テストには __様々な手法__ があります。今回の記事で説明するテストの手法は、__その中の一つにすぎません__。
:::

## テストケース

テストのために __作られたデータ__ の事を __テストデータ__ と呼び、それぞれのテストデータに対して関数呼び出しを行った際に、__期待される処理__ が行われているかどうかを確認します。テストデータと、期待される処理を組み合わせたものを、__テストケース__ と呼びます。

:::note
企業が作成するソフトなどで行われる、__本格的なテスト__ で用意するテストケースは、上記よりは __複雑__ なものになりますが、個人で行うような __小規模なソフトのテスト__ では、上記のような __シンプルなテストケース__ で __十分__ でしょう。参考までに、Wikipedia のテストケースの項目のリンクを下記に示します。
:::

https://ja.wikipedia.org/wiki/%E3%83%86%E3%82%B9%E3%83%88%E3%82%B1%E3%83%BC%E3%82%B9


期待される処理は __関数によって異なります__ が、多くのテストでは、関数が __特定の返り値を返すこと__ が期待する処理になります。今回の記事ではそのようなテストを紹介します。

:::note
他の期待される処理としては、__特定の文字列が表示される__ があり、実際に __これまでに行ってきた__ `display_board` メソッドの __確認作業__ などでは、__実行結果を見る__ ことで、__そのことを確認__ してきました。

他にも、`place_mark` のような __返り値を返さないメソッド__ の場合では、インスタンスの __属性の値__ が __特定の値に変化する__ などがあります。
:::

例えば、2 つの __実引数の合計__ を計算した値を __返り値として返す__ 関数であれば、以下の表のようなテストケース（テストデータと、期待される処理）が考えられます。下記の表は __一例__ で、下記の表 __以外__ のテストケースを __用意しても構いません__。

| テストデータ（実引数） | 期待される処理（返り値） |
|:-:|:-:|
| `1` と `2`  | `3`  |
| `2` と `3`  | `5`  |

__`judge` メソッド__ の場合は、下記の表のようなテストケースが考えられます。他にも、期待される処理が __引き分け__ を表す __`Marubatsu.DRAW`__ のテストケースが考えられるでしょう。

| テストデータ | 期待される処理（返り値） |
|:-:|:-:|
| 〇が勝利している `Marubatsu` クラスのインスタンス  | `Marubatsu.CIRCLE`  |
| ×が勝利している `Marubatsu` クラスのインスタンス  | `Marubatsu.CROSS`  |

## 全数テストと抜き取りテスト

上記の表のように、__一部の状況__ に対するテストケースを用意して行うテストの事を、__抜き取りテスト__ と呼びます。一方、__すべての状況__ に対するテストケースを用意してテストを行うことを、__全数テスト__ と呼びます。

__理想的__ には、全数テストを行うことで、__最も精度の高いテスト__ を行うことが出来ますが、__現実的__ には全数テストを行うことが __不可能__ または、__非常に困難__ な場合が良くあり、そのような場合は、抜き取りテストを行います。

例えば、先程の __2 つの実引数の合計__ を計算する関数の場合は、2 つの実引数の __組み合わせは無限__ にあるので全数テストを行う事は __不可能__ です。`judge` メソッドの全数テストを行う場合は、〇×ゲームの __すべての局面の数__ だけテストケースを用意する必要があり、その数は __膨大な数になる__[^1] ので全数テストを行うのは不可能ではありませんが __簡単ではありません__。そのため、本記事でも `judge` メソッドでは __抜き取りテスト__ を行います。

抜き取りテストでは、一般的に、テストケースが __多いほど__ テストの __精度が上がります__ が、その反面テストの __手間が大きくなる__ という欠点があります。また、やみくもにテストケースを増やせば良いというわけではなく、__品質の良いテストケース__ を用意することが __重要です__。品質の良いテストケースを用意する方法についてはこの後で説明します。

## テストの手順

関数のテストは、用意したすべてのテストケースに対して順番に、以下の手順で行います。

1. テストデータに対して __関数呼び出しを行う__
2. 関数呼び出しの結果、__期待される処理__ が行われたかどうかを __確認する__
3. 期待される処理が __行われていた場合__ は手順 5 へ
4. 期待される処理が __行われていない場合__ は、__エラーメッセージを表示__ する 
5. __次のテストケース__ に対して 手順 1 からの処理を行う

要約すると、「すべてのテストデータに対して __順番に関数呼び出しを実行__ し、期待される処理が __行われていない場合__ に __エラーメッセージを表示__ する」という処理を行います。

:::note warn
上記のテストで確認できるのは、関数がテストデータに対して、__期待される処理が行われる__ ということです。関数が __正しい処理__ を行うことを __確認__ できる __わけではない__ 点に注意して下さい。例えば、下記の `add` という関数は、3 行目の返り値を計算する式で、`+` の代わりに `*` を記述しているため __バグがあります__ が、テストデータとして、__2 つの `0`__ を実引数としてこの関数を呼び出すと、__偶然__ `0 + 0` と `0 * 0` の答えが等しいので、__期待される処理__ である `0` が __返されてしまいます__。そのため、このテストケースでテストを行っても __バグを発見できません__。

```python
def add(x, y):
    # この式は間違っている。正しくは return x + y
    return x * y 

print(add(0, 0))
```

テストケースを __増やす__ ことで、バグを発見できる __確率は高くなります__ が、実際に、テストケースを増やしてもバグを __発見できない場合があります__。先程のノートで、「テストによってすべてのバグを発見することができると __考えるべきではない__」と説明したのは、そのような場合があるからです。

:::

:::note
上記の手順を見て、期待される処理が __行われた場合__ にも __メッセージを表示する必要がある__ のではないかと __思う人がいる__ かもしれませんので補足します。

__数多くの__ テストケースに対して __期待される処理が行われた場合__ に __メッセージを表示する__ と、画面がメッセージで __埋め尽くされてしまう__ ことになります。テストで __知りたいこと__ は、__一般的__ に期待される処理が __行われなかったこと__ なので、期待される処理が行われたことを __知る必要がない__ 場合は、メッセージを __表示しない場合が多い__ ようです。

テストに __時間がかかる__ ような場合は、__途中経過を知らせる__ ために、期待される処理が __行われた数__ を表示するような場合もありますが、その際には画面がメッセージで埋め尽くされないような __工夫を行う__ のが一般的です
:::

# 制御フローテスト

関数のテストには、関数のブロックの中で行われる __処理を熟知__ した上で行う __ホワイトボックステスト__ と、関数の __利用方法だけを理解__ して行う __ブラックボックステスト__[^2] があり、それぞれでテストの __手法が異なります__。__自分で定義した関数__ の場合は、その中で行われる処理を熟知しているので、__本記事で行うテスト__ は __ホワイトボックステスト__ です。

ホワイトボックステストにもいくつか種類がありますが、本記事ではその中で良く使われ、比較的 __簡単に実施__ することが出来る __制御フローテスト__ について説明します。

__フロー__ とは、プログラムに記述した文を実行する __順序を表す流れ__（flow）の事で、[前回の記事](https://qiita.com/ysgeso/items/1fdd19037bbf64082a42#%E3%83%95%E3%83%AD%E3%83%BC%E3%83%81%E3%83%A3%E3%83%BC%E3%83%88) で説明した __フローチャート__ で図示することが出来ます。プログラムは、__通常は__ 最初の行から __順番に文が実行__ されますが、__if 文__、__for 文__、__関数呼び出し__ などによって、プログラムを実行する流れを __制御__ します。制御フローテストでは、それらの中で、if 文などによる __条件分岐__ によって __制御__ される __フロー__ に __注目したテスト__ を行います。

制御フローテストは、__テストデータに対する関数呼び出し__ によって、記述したプログラムの処理の __流れ__ や __実行__ が __どれだけ網羅されるか__ によって、__命令網羅__、__分岐網羅__、__条件網羅__ などの種類があります。制御フローテストは、網羅（coverage）をテストするので、__カバレッジテスト__ と呼ぶ場合があります。

制御フローテストは、__複雑なもの__ ほどテストの __精度が上がる__ 反面、テストケースを用意する __手間が増大する__ という欠点があるため、どれを選ぶかについては __状況に応じて選択する__ 必要があります。

今回の記事では、それぞれの種類について __目的、利点、欠点__ などの __性質__ について説明します。それぞれを使った具体的なテストの方法については次回の記事で説明します。

## 命令網羅（statement coverage）

__すべての文__ を最低でも __1 度は実行する__ テストです。文（statement）として記述された、すべての __命令を網羅__ して実行するので __命令網羅__ と呼びます。英語表記の頭文字をとって __SC__ や、__C0__ と略して呼ぶ場合があります。

### 命令網羅の目的

複数のテストデータを使って関数を呼び出した際に、関数のブロックの中で、__一度も実行されない文__ があった場合、__その文はテストされていない__ ということになります。命令網羅テストを行うことで、__テストされていない文が存在しない__ ことが保証されます。

### 命令網羅の例とフローチャート

命令網羅は、すべての文が実行されれば良いので、例えば、下記のような __`else` が記述されない__ `if` 文の場合は、__条件式が `True`__ になる __テストケースのみ__ が必要となります。

```python
if a > 1:
    print(a)
```

下図は、__条件式が `True`__ になる __1 つ__ の テストケースによって行われた命令網羅のテストのフローチャートです。__赤い線__ が行われた __処理の流れ__、__赤い枠の図形__ が実行された __処理と条件分岐__ を表します。命令網羅では、__すべての処理と条件分岐を実行する__ ので、下図のように、__すべての図形が赤い枠で囲まれます__。一方、下図で __灰色の線が存在__ するように、__すべての処理の流れが実行__ されるとは __限りません__。

下図には、条件式が 1 つしか存在しませんが、条件式が __複数あった場合も同様__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/485b35a2-1a57-1a0c-31ad-e712c26112b8.png" width="400">

### 命令網羅の性質

命令網羅は __最も単純__ な制御フローテストなので、用意する必要がある __テストケースの数が最も少なく__、__最も簡単に行う__ ことができますが、その反面テストの __精度は最も低くなります__。実際に、上記の例のフローチャートからわかるように、__すべての処理の流れ__ をテストできるとは __限りません__。

ただし、最も精度の低い命令網羅であっても、テストを __行わないよりまし__ です。実際にこれまでの記事で行った関数の確認作業の多くは命令網羅テストに相当するものでした。

なお、条件分岐が __一つも記述されていない__ 関数のテストは、命令網羅になります。


## 分岐網羅（decision coverage）

__すべての条件分岐__ の __条件式__ が __`True` と `False`__ の __それぞれで 1 度は実行__ されるようなテストです。条件分岐のすべての __分岐を網羅__ するので __分岐網羅__ と呼びます。英語表記の頭文字をとって __DC__ や、__C1__ と略して呼ぶ場合があります。

分岐網羅の事を、英語表記の decision から、__判定条件網羅__ と呼ぶ場合がありますが、おそらく次で説明する条件網羅と __名前が似ていて紛らわしい__ ので、分岐網羅のような __意訳__ のほうが良く使われるのでないかと思います。日本語の名前が紛らわしいと思った場合は、C0、C1 の表記を使うと良いでしょう。本記事でも、以降は命令網羅と分岐網羅の事を、__命令網羅(C0)__、__分岐網羅(C1)__ のように表記します。

### 分岐網羅(C1) の目的

複数のテストデータを使って関数を呼び出した際に、関数のブロックの中で、__一度も実行されない分岐__ があった場合、__その分岐はテストされていない__ ということになります。分岐網羅(C1) を行うことで、__テストされていない分岐が存在しない__ ことが保証されます。

すべての分岐を辿ることで、__すべての処理を実行する__ ことになるので、分岐網羅(C1) は命令網羅の条件を __必ず含みます__。

### 分岐網羅(C1) の例とフローチャート

先程のような、`else` が記述されていない `if` 文であっても、条件式が `True` と、`False` になる  __2 つのテストケースが必要__ になります。また、`if` 文に __`elif` が記述__ されている場合は、__その数だけ__ テストケースが必要になります。

下図は、__条件式が `True` と `False`__ になる __2 つの__ テストケースによって行われた分岐網羅(C1) のテストのフローチャートです。__赤の線__ と __赤の枠の図形__ が 条件式が __`True` の場合__、__緑の線__ と __緑の枠の図形__ が 条件式が __`False` の場合__ に行われた __処理の流れ、処理、条件分岐__ を表します。

分岐網羅(C1) では、__すべての処理と条件分岐が実行される__ ので、下図のように、__すべての図形__ が __赤または緑で囲まれます__。同様に、__すべての処理の流れが実行される__ ので、__すべての線__ が __赤または緑になります__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/386a619b-291c-0d1c-ab93-37e5afa45323.png" width="400">

### 分岐網羅(C1) の性質

__分岐網羅(C1) の条件__ を満たす場合は、__必ず命令網羅(C0) の条件も満たす__ ので、命令網羅(C0) よりも __少し複雑__ で __精度の高い__ テストを行うことが出来ます。そのため、一般的には、命令網羅(C0) よりも少し __多くのテストケースが必要__ となります。

条件式の中に __and や or 演算子__ が記述されるような、__複雑な条件文__ が記述されている場合は、分岐網羅(C1) だけでは __バグを見つけられない可能性がある__ ので、この後で説明する __複数条件網羅__(MCC) や __改良条件判断網羅__ などを使ったほうが良いでしょう。ただし、分岐網羅(C1) は、それらと比べて用意する必要がある __テストケースの数が少ない__ ので、そのような場合でも、時間がない場合や、条件文があまり複雑でない場合は、分岐網羅(C1) で済ますことがあります。

## 条件網羅（condition coverage）

条件分岐の __条件式の中__ に、__and または or__ の __論理演算子__ が記述されている場合、それらで連結された __それぞれの式__ が __`True` と `False`__ の __それぞれで 1 度は実行__ されるようなテストです。条件式の中の個々の __条件を網羅__ するので __条件網羅__ と呼びます。英語表記の頭文字をとって __CC__ や、__C2__ と略して呼ぶ場合があります。

なお、条件式の中に and または or の論理演算子が __記述されていない__ 場合は、条件網羅や、この後で説明する複数条件網羅(MCC) などのテストを __行うことはできません__。

本記事では、以後は条件網羅の事を、__条件網羅(C2)__ のように表記します。

### 条件網羅(C2) の目的

条件式の中に、__複数の式__ を and または or の論理演算子で連結して __記述する__ ということは、それらの式の計算結果が `True` になる場合と `False` になる場合を __想定しているはず__ です（理由は下記のノートを参照）。条件網羅(C2) を行うことで、条件式の中のすべての式が、__`True` と `False` の両方になる場合__ でテストが行われることが __保証されます__。

:::note
例えば、`2 > 1 and a > 1` のような条件式の場合、__`2 > 1`__ の部分は __必ず `True` になる__ ので、この条件式は `True and a > 1` となります。従って、この条件式の __`2 > 1`__ のように、__必ず__ `True` または `False` __になるような式__ は __記述する必要はありません__。上記の場合は、`a > 1` のように、その部分を __削除する__ ことが出来ます。 
:::

### 条件網羅(C2) の例

例えば、下記のプログラムでは、if 文の条件式の中に __or で連結__ された __`a > 1`__ と __`b > 2`__ という __2 つの式__ が存在します。この場合、この 2 つの式が `True` と `False` の __それぞれで 1 度は実行__ されるようなテストケースが必要になります。

```python
if a > 1 or b > 2:
    print(a, b)
```

条件網羅(C2) を満たすテストケースの例として、下記の表のような、__`a > 1` と `b > 2`__ が「__共に `True`__」、「__共に `False`__」 になるような __2 つ__ のテストケースが挙げられます。

| | `a > 1` | `b > 2` |
| :-: |:-:|:-:|
| __テストケース 1__ | `True`  | `True`  |
| __テストケース 2__ | `False`  | `False`  |

他にも、下記の表のようなテストケースを用意することで条件網羅(C2) を満たすテストを行うことが出来ます。`a > 1` と `b > 2` の列を __それぞれ縦に見た時__ に、__全体として__、`True` と `False` の __両方が含まれる__ ようなテストケース __であれば良い__ ということです。

| | `a > 1` | `b > 2` | `a > 1 or b > 2` |
| :-: |:-:|:-:|:-:|
| __テストケース 1__ | `True`  | `False`  | `True` |
| __テストケース 2__ | `False`  | `True`  | `True` |

### 条件網羅(C2) の性質

条件網羅(C2) の条件を満たしたからと言って、必ずしも分岐網羅(C1) や命令網羅(C0) の条件を __満たすとは限りません__。例えば、上記の __2 つ目の表__ のようなテストケースを用意した場合、表の右の列からわかるように、`a > 1 or b > 2` は __どちらのテストケースでも `True`__ になるので、この 2 つのテストケースで __条件網羅__(C2) のテストを __行うことはできません__。

条件網羅(C2) は __個人的__ には __中途半端な精度のテスト__ なので __あまり行われない__ と思います。本記事でも、条件網羅(C2) のテストは行いません。

### 複数の条件分岐が記述されている場合

具体例は示しませんが、__複数__ の条件分岐が記述されている場合は、それぞれの条件分岐で __個別に__ 条件網羅を満たすようなテストケースを用意します。ただし、それぞれの条件分岐で用意したテストケースで __内容が重複__ する場合は、__重複したものを削除__ します。これは、この後で紹介する、他の __条件網羅の性質を持つ__ テストでも __同様__ です。

## 判定条件/条件網羅 :（condition / decision coverage）

判定条件網羅（分岐網羅の別名）(C1) と条件網羅(C2) の両方を満たすようなテストです。英語表記の condition coverage と decision covarage の頭文字をとって __DC/CC__ や、__CDC__ と略して呼ぶ場合があります。日本語の場合は、__判断条件網羅__ や __条件判断網羅__ のように呼ぶ場合もあるようですが、どの __日本語の呼び方__ も、__意味が分かりづらい__ ので、どちらかの __英語表記の略__ を使ったほうが __わかりやすいかもしれません__。

本記事では以降は、判定条件/条件網羅のことを、__CDC__ と表記します。

### CDC の目的

CDC の目的は明確で、__分岐網羅__(C1) と、__条件網羅__(C2) の __両方の性質__ を持つテストを行うことです。

### CDC の例

```python
if a > 1 or b > 2:
    print(a, b)
```

上記の、先程のと同じプログラムの場合は、条件網羅(C2) の例で __最初に紹介__ した下記の表の 2 つのテストケースのように、条件式の中の __それぞれの式の計算結果__ と、__条件式の計算結果__ が、1 度は `True` と `False` になるようなテストケースが必要になります。

| | `a > 1` | `b > 2` | `a > 1 or b > 2` |
| :-: |:-:|:-:|:-:|
| __テストケース 1__ | `True`  | `True`  | `True`  |
| __テストケース 2__ | `False`  | `False`  | `False`  |

### CDC の性質

CDC は、分岐網羅(C1) と、条件網羅(C2) の両方の性質を満たしますが、and または or 演算子で連結された __式の値__ と、__条件式の値__ の __因果関係__ を __完全にチェックできない__ 可能性があるという __欠点__ があります。意味が分かりづらいと思いますので、具体例を挙げて説明します。

先程の、`a > 1 or b > 2` は、or 演算子で連結された、`a > 1` と `b > 2` が __両方とも `False`__ の場合は __`False`__、__片方でも `True`__ の場合は __`True`__ になるという条件式です。

__テストケース 2__ の場合は、`a > 1` と `b > 2` の両方が `False` なので、条件式の値が `False` になりますが、この時、__どちらの式の値__ を __`True` に変化__ させても、条件式の値が __`True` に変化__ します。このことから、`a > 1` が `False` であることと、`b > 2` が `False` であることの、__両方が原因__ で、__条件式の値が `False`__ になることがわかります。従って、テストケース 2 でテストを行うことで、`a > 1` と `b > 2` の __両方が `False`__  になっていることが __原因__ で、__条件式の計算結果 が `False` になる__ ことを確認することが出来ます。

一方、__テストケース 1__ の場合は、`a > 1` と `b > 2` の両方が `True` なので、条件式の値が `True` になりますが、この時、どちらか __片方の式の値__ を __`False` に変化__ させても、条件式の値は `True` のまま __変化しません__。このことから、このテストケースでテストを行っても、`a > 1` が `True` であることと、`b > 2` が `True` であることの、__どちらが原因__ で条件式の計算結果が `True` になるかを確認することが __出来ません__。

この因果関係が特定できないということが、テストを行う際に何故問題になるかについて、おそらくピンとこない人が多いのではないかと思いますので、別の例で説明します。

工場で __10 個__ の __ON/OFF ができるボタン__ が付いた装置が作られている状況を思い浮かべて下さい。この装置は、10 個のうち、__1 つ以上__ のボタンを __ON__ にすると __アラーム音が鳴り__、__すべてを OFF__ にすると __アラーム音が消える__ という装置です。__この装置__ が正しく動作するかどうかを __テスト__ する際に、以下の 2 種類のテストケースで十分だと思いますか？

* __すべてのスイッチを ON__ にした状態で __アラームが鳴る__ ことを確認する
* __すべてのスイッチを OFF__ にした状態で __アラームが消える__ ことを確認する

上記のテストケースによるテストは、10 個のスイッチを __すべて ON__ にすることで、__10 個のスイッチをまとめてテスト__ しているようなものですが、さすがにそれでは __不十分なテスト__ になってしまう事は誰でも直観的に理解できるでしょう。

実は、上記のようなテストは、__CDC__ の __性質を満たす__ テストを行っています。この装置を python のプログラムで表現すると、下記のような、if 文の __条件式__ で、__10 個の式__ を __or 演算子で連結__ したプログラムになります。

```python
if 式1 or 式2 or 式3 or 略 or 式10:
    アラームを鳴らす
else:
    アラームを消す
```

このプログラムに対して、下記の __2 種類__ の __テストケース__ でテストを行うことで、__CDC__ でテストを行うことができます。その理由は、__すべて__ の __式__ と、__条件式__ の値が __1 度は__ `True` と `False` に なるからです。

| | 式1 | 式2 | 略 | 式10 | 条件式 |
| :-: |:-:|:-:|:-:|:-:|:-:|
| __テストケース 1__ | `True`  | `True`  | `True`  | `True`  | `True`  |
| __テストケース 2__ | `False`  | `False` | `False` | `False`  | `False`  |

この 2 種類のテストケースは、先程の「すべてのスイッチが ON にした状態でアラームが鳴ることを確認する」、「すべてのスイッチを OFF にした状態でアラームが消えることを確認する」に __相当します__。このことから、CDC でテストを行うことが __不十分な場合がある__ ことが実感できたのではないでしょうか？


## 複数条件網羅（multiple condition coverage）

条件分岐の __条件式の中__ に、__and または or__ の __論理演算子__ が記述されている場合、それらで連結された __それぞれの式__ が __`True` と `False`__ の __すべての組み合わせ__ で __1 度は実行__ されるようなテストです。英語表記の頭文字をとって __MCC__ と略して呼ぶ場合があります。

本記事では以降は、複数条件網羅のことを、__複数条件網羅(MCC)__ と表記します。

:::note
複数条件網羅(MCC) のことを C2 と呼ぶ __場合もある__ ようです。フロー制御テストの用語は、__統一されていないものがある__ ようなので、それらの用語を使用する場合は、用語がどのような意味で使われているかを __確認したほうが良い__ でしょう。
:::

### 複数条件網羅(MCC) の目的

複数条件網羅(MCC) によるテストでは、それぞれの式が `True` と `False` の すべての組み合わせで 1 度は実行されるので、条件式の計算結果が __1 度は `True` と `False`__ になります。従って、__複数条件網羅__(MCC) は、__分岐網羅__(C1) と __条件網羅__(C2) の __両方の性質__ を持ちます。また、すべての組み合わせで __漏れなく確認する__ ので、それぞれの式の値と、条件式の値の因果関係を __気にする必要はありません__。

:::note
条件式の計算結果が  __1 度は `True` と `False` になる__ 理由は、and、or 演算子はいずれも、__何らかの組み合わせ__ で __必ず__ `True` と `False` の __両方の値を取る__ からです。
:::


### 複数条件網羅(MCC) の例

条件式が `a > 1 or b > 2` の場合は、下記の表の __4 通り__ のテストケースを用意する必要があります。

|| `a > 1` | `b > 2` |
|:-:|:-:|:-:|
| __テストケース 1__ | `True`  | `True`  |
| __テストケース 2__ | `True`  | `False`  |
| __テストケース 3__ | `False`  | `True`  |
| __テストケース 4__ | `False`  |  `False` |

### 複数条件網羅(MCC) の性質

複数条件網羅(MCC) は、制御フローテストの中で、最後に説明する、経路組み合わせ網羅の次に __精度の高い__ テストを行うことが出来ますが、and や or で連結された式が __`n` 個__ の場合は、__$2^n$ 個__ のテストケースが必要になるという欠点があります。例えば、$n = 10$ の場合は、1024 個ものテストケースが必要になります。このような、__組み合わせの数が膨大になる__ ことを、__組み合わせ爆発__ と呼びます。

複数条件網羅(MCC) は __組み合わせ爆発が起きやすい__ ので、__現実的__ にテストケースを用意することが __不可能な場合__ が良くあります。

## 改良条件判断網羅（modified condition / dicision coverage）

条件分岐の __条件式の中__ に、__and または or__ の __論理演算子__ が記述されている場合、それらで連結された __それぞれの式の値__ が、__全体の条件式__ の計算結果に __影響を及ぼす__（因果関係がある）かどうかを __考慮__ したテストを行います。英語表記の頭文字をとって __MC/DC__ と略して呼ぶ場合があります。他にも、改良条件判定と呼ばれる場合もあるようです。

表記が長いので、本記事では以降は改良条件判断網羅のことを、__MC/DC__ と表記します。

### MC/DC の目的

MC/DC の目的は、__分岐網羅__(C1) と __条件網羅__(C2) の __両方の性質を満たす__ テストを、__最小限のテストケース__ で行うことです。

### MC/DC のテストケースの求め方

`a > 1 or b > 2 or c > 3` を例に説明します。この条件式の中の、それぞれの __式の計算結果__ の __すべての組み合わせ__ は下記の表のように 8 通りの組み合わせになります。それぞれを __区別できるように__、表の左の列に __番号__ を付けてあります。

| 番号 | `a > 1` | `b > 2` | `c > 3` | 条件式の値 |
|:-:|:-:|:-:|:-:|:-:|
|1| `True`  | `True`  | `True`  | `True`  |
|2| `True`  | `True`  | `False`  | `True`  |
|3| `True`  | `False`  | `True`  | `True`  |
|4| `True`  | `False`  | `False`  | `True`  |
|5| `False`  | `True`  | `True`  | `True`  |
|6| `False`  | `True`  | `False`  | `True`  |
|7| `False`  | `False`  | `True`  | `True`  |
|8| `False`  | `False`  | `False`  | `False`  |

表からわかるように、この条件式は、__`a > 1` が `True`__ の場合は、__残りの__ `b > 2` と `c > 3` の __計算結果に関わらず__、__`True` になります__。従って、`a > 1` が `True` の場合は、残りの式の計算結果は、__条件式の値__ に __影響を与えない__ ので、__`a > 1` が `True` であること__ が、条件式の値が `True` であることの __原因__ と __考える__ ことが出来ます。

このことから、__`a > 1` が `True`__ である、1 ~ 4 番の __4 つのテストケース__ は下記の表のように、__一つにまとめる__ ことが出来ます。表の __Any__ は、`True` か `False` の __どちらでも良い__、すなわち条件式の値に __影響を与えない__ ということを表します。Python に Any という特殊なデータが __存在するわけではない__ 点に注意して下さい。

| 番号 | `a > 1` | `b > 2` | `c > 3` | 条件式の値 |
|:-:|:-:|:-:|:-:|:-:|
|1 ~ 4| `True`  | Any  | Any  | `True`  |
|5| `False`  | `True`  | `True`  | `True`  |
|6| `False`  | `True`  | `False`  | `True`  |
|7| `False`  | `False`  | `True`  | `True`  |
|8| `False`  | `False`  | `False`  | `False`  |

:::note
[前回の記事](https://qiita.com/ysgeso/items/1fdd19037bbf64082a42#and-%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%A8-or-%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%AE%E7%9F%AD%E7%B5%A1%E8%A9%95%E4%BE%A1) で説明したように、Python では and と or 演算子による演算を行う際に、__短絡評価__ が行われることがあるので、__`a > 1` が `True` の場合__ は、実際に `b > 2` と `c > 3` は、いずれも __計算が省略__ されます。従って、上記の表のように、1 ~ 4 のテストケースを 1 つにまとめることは、__理にかなっています__。
:::

`a > 1` が `False` の場合は、それだけでは __条件式の値は決まらない__ ので、残りの式の計算結果を調べる必要があります。そこで、次に __`b > 2` が `True`__ の場合を考えることにします。先程と __同様の理由__ で、この場合は `c > 3` の __計算結果に関わらず__、__条件式の値が `True`__ になります。従って、先程と同様に、__5 と 6__ のテストケースは、下記の表のように、__一つにまとめる__ ことが出来ます。

| 番号 | `a > 1` | `b > 2` | `c > 3` | 条件式の値 |
|:-:|:-:|:-:|:-:|:-:|
|1 ~ 4| `True`  | Any  | Any  | `True`  |
|5、6| `False`  | `True`  | Any  | `True`  |
|7| `False`  | `False`  | `True`  | `True`  |
|8| `False`  | `False`  | `False`  | `False`  |

`a > 1` と `b > 2` が __共に `False`__ の場合は、`c > 3` の __計算結果__ によって、__条件式の値が変化する__ ので、7 と 8 を __まとめることはできません__。

上記の事から、`a > 1 or b > 2 or c > 3` を、MC/DC でテストする際に必要なテストケースは、上記の表の 4 つであることがわかります。表の、1 ~ 4 に対応するテストケースは、1 ~ 4 のうちの __どれか 1 つを選択する__ ことになります。5、6 も同様です。

:::note
上記の例では、__先頭の `a > 1` から順番__ に調べていきましたが、`b > 2` や `c > 3` から調べても __論理的には__ MC/DC のテストを行うためのテストケースを求めることができます。ただし、Python の and と or 演算子では、短絡評価が行われるので、先頭の式から順番に調べないと __問題が発生する場合がある__ ので、MC/DC のテストケースを求める場合は、__先頭の式から調べるようにして下さい__。
:::

### MC/DC に必要なテストケースの数

and 演算子を使った条件式や、and と or 演算子が混在する条件式の場合でも、__同様の方法__ で、__必要最小限のテストケース__ を求めることが出来ます。証明は省略しますが、MC/DC に __必要なテストケースの数__ は、and と or 演算子で連結された式の数を $n$ とすると、__n + 1 個__ のテストケースが必要となります。従って、この場合のMC/DC によるテストは、複数条件網羅(MCC) によるテストのような __組み合わせ爆発__ は __発生しません__。

上記の説明を見て、MC/DC のテストケースを用意する手順が複雑で大変だと思った方が多いのではないかと思います。実際に、MC/DC のテストケースを用意するのは大変なのですが、条件式の中で、__or 演算子のみ__ または、__and 演算子のみ__ が使われている場合は、__簡単な手順__ でテストケースを求めることが __できる__ ので、その方法について紹介します。

残念ながら、or と and 演算子が __混在する場合__ は、__先程の手順__ を使ってテストケースを求める __必要__ があります。フリーのものを見つけることはできませんでしたが、そのようなテストケースを求めるツールを利用することが出来るのであれば、利用したほうが良いでしょう。また、組み合わせ爆発が __起きない場合__ は __複数条件網羅__(MCC) でテストを行ったほうが __簡単な場合__ もあります。

:::note
参考までに、MC/DC の手順を説明したページのリンクを紹介します。このページの最後に、MC/DC のテストケースを求めるツールが言及されていますが、おそらく有料のツールではないかと思われます。
:::

https://hldc.co.jp/wp-content/uploads/2021/08/05_HeartlandData_WhitePaper_mcdc_dt10ae.pdf

### or 演算子のみで連結された場合

`a > 1 or b > 2 or c > 3` は、`a > 1`、`b > 2`、`c > 3` の __3 つの式__ が __or 演算子で連結__ しています。or 演算子は、すべての式が `False` の場合は `False` に、一つ以上の式が `True` であれば `True` になるような演算を行うので、この条件式は __値が `True`__ になる __式の数__ によって、以下の表のように 4 種類に分類できます。

| 値が `True` になる式の数 | 条件式の計算結果 |
|:-:|:-:|
| 0  | `False`  |
| 1  | `True`  |
| 2  | `True`  |
| 3  | `True`  |

式の値を __1 つだけ__、`True` から `False` または、`False` から `True` に __変化させた場合__ は、__値が `True`__ になる __式の数__ が __1 つだけ増減__ します。上記の表から、式の値を __1 つだけ変化__ させた場合に、__条件式の計算結果が変化__ するのは、値が `True` になる式の数が、「__0 から 1 になった場合__」と「__1 から 0 になった場合__」だけです。値が `True` になる式の数が __2 つ以上__ あった場合は、式の値を __1 つだけ変化__ させた場合に、条件式の計算結果が __変化する__ ことは __決してありません__。

このことから、以下のことがわかります。

* 値が `True` になる __式の数が `0`__ の場合は、どの式の値を変化させても条件式の計算結果に __影響を与える__ のでテストケースとして __必要__ である
* 値が `True` になる __式の数が `1`__ の場合は、値が __`True` の式を `False` に変化__ させることで、条件式の計算結果に __影響を与える__ のでテストケースとして __必要__ である
* 値が `True` になる __式の数が `2` 以上__ の場合は、どの 1 つの式の値を変化させても、条件式の計算結果に __影響を与えることは無い__。従って、そのようなデータは、テストケースとして __必要ではない__

従って、`a > 1 or b > 2 or c > 3` を MC/DC でテストを行う際に必要となるテストケースは、__値が `True`__ になる __式の数__ が __0 または 1__ のデータで、そのテストケースは下記の表の __4 種類__ になります。

| `a > 1` | `b > 2` | `c > 3` | 条件式の値 |
|:-:|:-:|:-:|:-:|
| `False`  | `False`  | `False`  | `False`  |
| `True`  | `False`  | `False`  | `True`  |
| `False`  | `True`  | `False`  | `True`  |
| `False`  | `False`  | `True`  | `True`  |

上記の方法で求めたテストケースが、__分岐網羅__(C1) と __条件網羅__(C2) の __両方の性質を満たす__ ことを示します。

「値が `True` になる 式の数が 0 の場合は、条件式の値が `False`」に、「値が `True` になる 式の数が 1 の場合は、条件式の値が `True`」になるので、__分岐網羅__(C1) の性質を満たします。

また、__それぞれの式の値__ は、テストケースのうちの __1 つだけが `True`__ になり、__それ以外__ のテストケースは __`False`__ になるので、__条件網羅__(C2) の性質を満たします。

上記の例では、or 演算子で連結された式の数が 3 つでしたが、__式の数が増えた場合__ でも __同様の方法__ で、__式の数 + 1 個__ のテストケースを用意することで、MC/DC テストを行うことが出来ます。これは、次に説明する __and 演算のみで連結__ された場合も __同様__ です。

### and 演算子のみで連結された場合

__and 演算子のみで連結__ された条件式の場合も同様です。例えば、`a > 1 and b > 2 and c > 3` は、`a > 1`、`b > 2`、`c > 3` の __3 つの式__ が __and 演算子で連結__ しています。and 演算子は、すべての式が `True` の場合は `True` に、一つ以上の式が `False` であれば `False` になるような演算を行うので、この条件式は __値が `False`__ になる __式の数__ によって、以下の表のように 4 種類に分類できます。

| 値が `False` になる式の数 | 条件式の計算結果 |
|:-:|:-:|
| 0  | `True`  |
| 1  | `False`  |
| 2  | `False`  |
| 3  | `False`  |

or 演算子の場合と同様の理由で、`a > 1 and b > 2 and c > 3` を MC/DC でテストを行う際に必要となるテストケースは、__値が `False`__ になる __式の数__ が __0 または 1__ のデータで、そのテストケースは下記の表の __4 種類__ になります。

| `a > 1` | `b > 2` | `c > 3` | 条件式の値 |
|:-:|:-:|:-:|:-:|
| `True`  | `True`  | `True`  | `True`  |
| `False`  | `True`  | `True`  | `False`  |
| `True`  | `False`  | `True`  | `False`  |
| `True`  | `True`  | `False`  | `False`  |

## 経路組み合わせ網羅（path coverage）

条件網羅(C2) では、__1 つの条件分岐の中__ の組み合わせを考えていましたが、経路組み合わせ網羅では、__可能な限り__、複数の __条件分岐__ の __全ての経路__ の __組み合わせ__ を網羅します。__経路__（path）を __考慮__ するので、英語では、パス・カバレッジと呼びますが、個人的には __PC__ と略して表記するのはあまり見たことがありません。

なお、上記で __可能な限り__ と書いたのは、条件分岐の条件式によって、__通ることが不可能な経路__ が存在する __場合がある__ からです。具体例については、この後で説明します。

### 経路組み合わせ網羅の目的

関数の中に、複数の条件分岐が記述されている場合、__前の条件分岐の処理__  が、__その後の条件分岐の処理__ に __影響を与える__ 場合があります。そのような場合は、それらの __条件分岐の経路__ の __組み合わせ__ をテストする必要があります。具体例については次で説明します。

### 経路組み合わせ網羅の例

条件式の中に or や and 演算子が記述されていると複雑になるので、下記の例ではそれらが __記述されていない__ 条件分岐で説明します。従って、下記の例の場合は、条件網羅(C2) の性質を持つテストに関する内容は出てきません。

下記のプログラムは、以下のような計算を行う `calc_a` という関数を定義しています。なお、下記のプログラムは、経路組み合わせ網羅が __必要な例__ として作ったものなので、行っている計算に __深い意味はありません__。

* 仮引数 `a` の値が 正の場合は、`a` から `5` を引く 
* その結果、`a` の値が 正の場合は、`a` に `5` を足す
* `a` を返り値として返す

```python
def calc_a(a):
    if a > 0:
        a -= 5

    if a > 0:
        a += 5

    return a
```

最初の if 文の条件分岐だけに注目すると、行われる計算は以下の表のようになります。

| `a` の値 | 最初の if 文の処理後の `a` |
|:-:|:-:|
| `0 < a` | `a - 5`   | 
| `a <= 0`  | `a`  |

次の if 文の条件分岐では、`a > 0` を調べるので、__最初の if 文の処理の結果__、`a > 0` になる __条件を考える__ 必要があります。その条件を考えると、以下の表のようになります。

| 最初の `a` の値 | 最初の if 文の処理後の `a` | 次の if 文の処理後の `a`（返り値） |
|:-:|:-:|:-:|
| `5 < a` | `a - 5` （正の値）  | `a` | 
| `0 < a <= 5` | `a - 5` （0 以下）  | `a - 5` | 
| `a <= 0`  | `a` （0 以下） | `a` |

つまり、この関数は、仮引数 `a` の値が、__`0 < a <= 5`__ の場合は __`a - 5`__ を返り値として返し、__それ以外の場合__ は、__`a`__ を返すという処理を行います。

この関数に対して __分岐網羅(C1)__ でテストを行う場合は、下記のような 2 つのテストケースを用意すれば十分です。

|| 1 つめの `a > 0` | 2 つめの `a > 0` |
|:-:|:-:|:-:|
| __テストケース 1__ | `True`  | `True`  |
| __テストケース 2__ | `False`  | `False`  |

具体的なテストケースとして以下の表のような 2 つのテストケースが考えられます。

|| テストデータ（仮引数 `a`） | 期待される値（返り値） |
|:-:|:-:|:-:|
| __テストケース 1__ | `100`  | `100`  |
| __テストケース 2__ | `-100`  | `-100`  |

上記のテストケースは、分岐網羅(C1) の条件を満たしていますが、先程の表の `0 <= a < 5` の場合の __経路__ のテストが __行われていません__ 。これが __分岐網羅__(C1) の __問題点__ です。

__経路組み合わせ網羅__ の場合は、下記の表のような、条件分岐の __全ての分岐の組み合わせ__ の経路を通るテストケースを用意します。ただし、実際には、先程の表からわかるように、最初の `a` がどのような値をとっても、テストケース 3 を満たすような `a` は __存在しません__。従って、実際には下記の表の、テストケース 1、2、4 を満たすような __3 つの `a`__ をテストケースとします。

|| 1 つめの `a > 0` | 2 つめの `a > 0` | 最初の `a` の値 |
|:-:|:-:|:-:|:-:|
| __テストケース 1__ | `True`  | `True`  | `5 < a` |
| __テストケース 2__ | `True`  | `False`  | `0 < a <= 5` | 
| __テストケース 3__ | `False`  | `True`  | 存在しない |
| __テストケース 4__ | `False`  | `False`  | `a <= 0` |

### 経路組み合わせ網羅の性質

経路組み合わせ網羅は、__最も精度の高いテスト__ を行うことが出来ますが、それぞれの条件分岐の経路を全て __乗算した数__ だけ、テストケースが必要となるので、__組み合わせ爆発__ が __非常に起きやすい__ という欠点があります。上記の例では、それぞれの条件式が単純な式でしたが、and や or 演算子が記述されていた場合は、全体のテストケースの数が __膨大な数__ になる可能性が高くなります。そのため、テストの __精度が求められない__ 場合は、経路組み合わせ網羅によるテストは一般的には __行われません__。

組み合わせ爆発を防ぐ工夫として、お互いに __影響を与える可能性__ がある条件分岐に __絞って__、条件式経路組み合わせ網羅を行うという方法もあります。

# テストの手法のまとめ

今回の記事で紹介したテストの手法を表にまとめます。なお、テストケースの数と、精度に関しては __目安__ なので、状況によっては異なる場合がある点に注意して下さい。

| 名称 | 略称 | テストケースの数 | 精度 |
|:-:|:-:|:-:|:-:|
| 命令網羅  | C0、SC  | 最小  | 最小 |
| 分岐網羅  | C1、DC  | 小  | 小  |
| 条件網羅  | C2、CC  | 小~中  | 小  |
| 判定条件/条件網羅  | CDC  | 小~中  | 中  |
| 複数条件網羅  | MCC  | 大  | 大  |
| 改良条件判断網羅  | MC/DC  | 中  | 大  |
| 経路組み合わせ網羅  | なし  | 最大  | 最大  |

多くの手法を説明しましたが、実際にどれを使うかについては、__自分で判断する__ しかありません。__個人的__ には、__あまり複雑でない__ 関数の場合は __分岐網羅__(C1)、`judge` メソッドのように、実装したプログラムが正しく動作するか __自信が持てない__ ような __複雑な関数__ の場合は __改良条件判断網羅__(MC/DC)、特に高い精度が求められたり、バグがどうしても見つからないような場合は __経路組み合わせ網羅__ でテストを行えば良いのではないかと思います。それ以外の手法も __状況に応じて使い分ける__ ことが出来るようになるのが理想なので、時間に余裕がある方は、次回の記事でテストの具体的な方法を学んだ後で、自分で作った関数に対して、上記の様々な手法でテストを行ってみることをお勧めします。

ただし、時間は有限なので、テストに __時間をかけすぎる__ のも __良くない__ と思います。どの程度の関数で、どの程度のテストを行う（もしくは行わない）べきかについては、__経験から学ぶ__ 以外の __方法はない__ ので、頑張って経験を積んで自分なりのテストの方法を見つけて下さい。

# 次回の記事について

長くなったので今回の記事はここまでにします。次回の記事では、今回の記事で説明した手法を使って、`judge` メソッドのテストを行います。

# 本記事で入力したプログラム

今回の記事には、JupyterLab のファイルと marubatsu_new.py はありません。

# 次回の記事

https://qiita.com/ysgeso/items/85235f2aab2248500bc8

[^1]:具体的な〇×ゲームの局面の数については、〇×ゲームの AI を作る際に計算して示すことにします
[^2]:中で行われる __具体的な処理を知らずに利用する関数__ のようなもののことを、中身が見えない黒い（black）箱（box）に例えて __ブラックボックス__ と呼びます。ホワイトボックスは __その反対__ で、中で行われる処理を熟知して利用する関数のようなものことを表します