Pythonで〇×ゲームのAIを一から作成する　その26　テストケースを表すデータ構造の修正

# 目次と前回の記事

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

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

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

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

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

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

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

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

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

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

https://github.com/ysgeso/marubatsu/blob/master/026/test.py

## 前回までのおさらい

前回の記事では、[命令網羅(C0)](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E5%91%BD%E4%BB%A4%E7%B6%B2%E7%BE%85statement-coverage) で `judge` メソッドのテストを開始しました。

最初に 〇 が勝利した場合のテストケースでテストし、その後で、今後のテストを効率よく行うための `test_judge` という関数を定義しました。

# `judge` メソッドのテスト
## 命令網羅(C0) によるテスト（続き）

### × が勝利した場合のテスト

× が勝利した場合のテストも、〇 が勝利した場合のテストと同様に、下図右のような、0 行に 3 つ × が並んだ状態のテストケースで行うことにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3eb36f51-2764-d1a7-5054-72ddab8b6625.png" width="250">

下記は、上図左の順番でマークを配置するためのテストケースを表すデータを `testcase` に代入し、`test_judge` を呼び出すことでテストを行うプログラムです。実行結果からわかるように、表示されるゲーム盤は __確かに 0 行に 3 つ × が並んでいます__ が、その下に __`error!` が表示されてしまいます__。その原因について考えてみて下さい。

```python
from marubatsu import Marubatsu
from test import test_judge

testcase = [
    [ 0, 1 ], # 着手順とゲーム盤
    [ 0, 0 ], # 246  xxx
    [ 1, 1 ], # 13.  oo.
    [ 1, 0 ], # 5..  o..
    [ 0, 2 ],
    [ 2, 0 ],
]

test_judge(testcase)
```
実行結果
```
Turn o
xxx
oo.
o..

error!
```

エラーが発生する原因は、`test_judge` の 7 行目の if 文が、`if mb.judge() == Marubatsu.CIRCLE:` のように、__`mb.judge()` の返り値__ が __〇 の勝利__ を表す
 `Marubatsu.CIRCLE` と等しいことを __判定__ しているため、__〇 の勝利__ の場合は __`"ok"`__ を、__そうでない__ 場合は __`"error!"`__ を表示するようになっていることです。今回のテストでは __× の勝利を判定__ したいので、この部分を修正する必要があります。

ただし、7 行目を `if mb.judge() == Marubatsu.CROSS:` のように書き換えてしまうと、今度は __〇 の勝利の判定__ に `test_judge` を __使うことが出来なく__ なってしまいます。

#### テストケースを表すデータ構造の修正 その1

この問題は、`testcase` の中に、__テストデータしか__ 代入されていないことが原因です。[以前の記事](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E3%83%86%E3%82%B9%E3%83%88%E3%82%B1%E3%83%BC%E3%82%B9) で説明したように、テストケースは、__テストデータ__ と、__期待される処理__ の __組み合わせ__ であることを思い出してください。

そこで、`testcase` の中に、__期待される処理__ を表す __データを組み込む__ ように、テストケースを表す __データ構造を修正__ することにします。期待される処理を表すデータは、__`judge` メソッドの返り値__ なので、例えば、先程の __× が勝利__ する `testcase` の中に、`Marubatsu.CROSS` のデータを組み込む必要があります。そのために、どのようなデータ構造にすれば良いかについて少し考えてみて下さい。

なお、[前回の記事](https://qiita.com/ysgeso/items/85235f2aab2248500bc8#%E5%91%BD%E4%BB%A4%E7%B6%B2%E7%BE%85c0-%E3%81%AB%E3%82%88%E3%82%8B%E3%83%86%E3%82%B9%E3%83%88) では、『__テストで利用する__「特定のマスにマークが配置された `Marubastu` クラスのインスタンス」のことを、単に __テストケース__ と表記します』のように説明しましたが、以後は 、上記のデータに、__期待される処理を表さすデータ__ を __組み込んだもの__ のことを __テストケース__ と表記することにします。同様に、__テストケースを表すデータ__ も、__期待される処理を表すデータ__ を __組み込んだもの__ とします。

簡単に思いつく方法として、下記のプログラムのように、__現状の__ `testcase` を表す list の __最後の要素__ に、期待される処理を表すデータを __追加する__ という方法があるでしょう。

:::note
同様の方法として、__先頭の要素__ に追加するという方法も考えられます。
:::

```python
testcase = [
    [ 0, 1 ], # 着手順とゲーム盤
    [ 0, 0 ], # 246  xxx
    [ 1, 1 ], # 13.  oo.
    [ 1, 0 ], # 5..  o..
    [ 0, 2 ],
    [ 2, 0 ],
    Marubatsu.CROSS, # 最後の要素が、期待される処理を表す
]
```

`testcase` の __データ構造が変化__ したので、`test_judge` も __それに合わせて修正__ する必要があります。具体的には、__期待された処理__ を表す `testcase` に代入された list の __最後の要素__ を __取り出して__、__そのデータ__ を使って __テストの判定を行う__ ように修正します。

そのような処理は、list の __最後の要素を削除__ し、__返り値として返す__、__`pop`__[^1] という  __list のメソッド__ を利用することで記述することが出来ます。

`pop` メソッドの具体的な使用例を挙げます。下記のプログラムは、1 行目で、`a` に `[1, 2, 3]` という list を代入し、2 行目で、`pop` メソッドを呼び出して返り値を `last` に代入しています。実行結果から、`last` には、`a` の __最後の要素__ であった `3` が __代入__ され、`a` に代入された list から、__最後の要素が削除__ されていることがわかります。

```python
a = [1, 2, 3]
last = a.pop()
print(last)
print(a)
```
実行結果
```
3
[1, 2]
```

:::note
tuple は __[イミュータブル](https://qiita.com/ysgeso/items/cc9e67c16b0814c8bc2e#%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB%E3%81%A8%E3%82%A4%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB)__ なデータなので、__要素を変化__ させる `pop` メソッドを __利用__ することは __できません__。tuple を使ってテストケースを表すデータを表現した場合は、後述のノートで説明する方法で、`test_judge` を修正する必要があります。
:::

下記は、`pop` メソッドを使って `test_judge` を修正したプログラムです。2 行目で `pop` メソッドを使って、`testcase` に代入された list の __最後の要素__ を取り出して __`winner`__ に __代入__ しています。その結果、`testcase` の値は、__期待される処理の値__ を __組み込む前__ のデータに __戻る__ ので、3 ~ 6 行目のプログラムを __修正する必要はありません__。

8 行目では、`judge` メソッドの返り値を判定する条件式を、先程 __`winner`__ に代入した __期待される処理__ を表すデータを __使って判定__ するように修正しています。

```python
 1  def test_judge(testcase):
 2      winner = testcase.pop()
 3      mb = Marubatsu()
 4      for x, y in testcase:
 5          mb.move(x, y)
 6      print(mb)
 7
 8      if mb.judge() == winner:
 9          print("ok")
10      else:
11          print("error!")
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def test_judge(testcase):
    winner = testcase.pop()
    mb = Marubatsu()
    for x, y in testcase:
        mb.move(x, y)
    print(mb)

    if mb.judge() == winner:
        print("ok")
    else:
        print("error!")
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def test_judge(testcase):
+   winner = testcase.pop()
    mb = Marubatsu()
    for x, y in testcase:
        mb.move(x, y)
    print(mb)

-   if mb.judge() == Marubatsu.CIRCLE:
+   if mb.judge() == winner:
        print("ok")
    else:
        print("error!")
```
</details>

下記のプログラムの実行結果から、正しく動作することが確認できます。

```python
test_judge(testcase)
```
実行結果
```
Turn o
xxx
oo.
o..

ok
```

:::note
`testcase` を表す list の __先頭の要素__ に、期待される処理を表すデータを追加した場合も、`pop` メソッドを利用して `test_judge` を修正することが出来ます。

`pop` メソッドは、__実引数に整数__ を記述することで、__実引数__ で記述した __インデックスの要素を削除__ し、返り値として返す処理を行います。先頭の要素の __インデックスは `0`__ なので、先頭の要素に、期待される処理を表すデータを追加した場合は、`test_judge` の 2 行目を __`winner = testcase(0)`__ のように修正します。

`pop` メソッドの詳細は、下記のリンク先を参照して下さい。
:::

https://docs.python.org/ja/3/tutorial/datastructures.html#more-on-lists

#### 上記の修正方法の欠点

list の __`pop` メソッド__ は、list の __要素を削除__ してしまうので、`testcase` の __内容が変化__ してしまうという __欠点__ があります。具体的には、下記のプログラムのように、`test_judge` を呼び出す __前後__ で __`testcase`__ の内容を __表示__ すると、実行結果から、`test_judge` を呼び出した __後__ で、`testcase` の __値が変化する__ ことが確認できます。

```python
testcase = [
    [ 0, 1 ], # 着手順とゲーム盤
    [ 0, 0 ], # 246  xxx
    [ 1, 1 ], # 13.  oo.
    [ 1, 0 ], # 5..  o..
    [ 0, 2 ],
    [ 2, 0 ],
    Marubatsu.CROSS, # 最後の要素が、期待される処理を表す
]

print("befor test_judge:", testcase)
test_judge(testcase)
print("after test_judge:", testcase)
```
実行結果
```
befor test_judge: [[0, 1], [0, 0], [1, 1], [1, 0], [0, 2], [2, 0], 'x']
Turn o
xxx
oo.
o..

ok
after test_judge: [[0, 1], [0, 0], [1, 1], [1, 0], [0, 2], [2, 0]]
```

そのため、上記のプログラムを __実行した後__ で、下記のプログラムのように、__もう一度__ `test_judge` を呼び出すと、先程の `test_judge` の呼び出しによって、`testcase` の値が __変化した__ ため、今度は __"error!"__ が実行結果に __表示__ されてしまいます。

また、気づきにくいかもしれませんが、__右上の (2, 0)__ のマスに __× が配置されなく__ なります。これは、`test_judge` の 2 行目で `testcase.pop()` が実行された結果、右上の (2, 0) のマスを表す `testcase` の __最後の要素の `[2, 0]` が削除__ されるからです。

```python
test_judge(testcase)
```
実行結果
```
Turn x
xx.
oo.
o..

error!
```

`test_judge` を呼び出しても `testcase` の値が __変化しないようにする__ ために、`pop` メソッドを使わない方法で `test_judge` を修正することは可能です（その方法はこの後のノートで紹介します）が、__テストケースに必要なデータ__ と、__期待される処理を表すデータ__ を、__同じ list__ の要素の中に __混在させる__ ようなデータ構造は、__バグの原因__ になる __可能性が高くなります__。その理由については、この後で説明します。

修正その 1 のデータ構造は、__簡単に思いつく__ ことが出来る __可能性が高い__ ので紹介しましたが、本記事では、修正その 1 の修正方法は採用せず、次に説明する __別の方法__ でテストケースを表すデータ構造を修正することにします。

バグの原因になる可能性が高い、修正その 1 のデータ構造を紹介した他の理由は、__同じデータ__ に対して __様々なデータ構造で表現する方法__ を知っておくことが、プログラミングを行う上で __重要になる__ からです。また、修正その 1 のようなデータ構造が、__常に悪い__ データ構造になる __わけではありません__ ので、体験することは __無駄にはなりません__。

:::note
list の __スライスという表記__ などを用いることで、`test_judge` を呼び出しても `testcase` の値が __変化しない__ ように修正することができます。記事が長くなるので今回の記事では下記のプログラムの意味は説明しません（コメントに簡単な説明を入れておきました）が、list のスライスなどは実際に __良く使われる__ ので必要になった時点で詳しく紹介することにします。

`pop` メソッドが利用できない tuple の場合も、下記の方法で修正できます。

```python
def test_judge(testcase):
    winner = testcase[-1]     # 最後の要素を winner に代入する
    testcase = testcase[:-1]  # 最後の要素を除いたものを testcase に代入する
    mb = Marubatsu()
    for x, y in testcase:
        mb.move(x, y)
    print(mb)

    if mb.judge() == winner:
        print("ok")
    else:
        print("error!")
```

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def test_judge(testcase):
-   winner = testcase.pop()
+   winner = testcase[-1]     # 最後の要素を winner に代入する
+   testcase = testcase[:-1]  # 最後の要素を除いたものを testcase に代入する
    mb = Marubatsu()
    for x, y in testcase:
        mb.move(x, y)
    print(mb)

    if mb.judge() == winner:
        print("ok")
    else:
        print("error!")
```
</details>


下記の実行結果から、`test_judge` が正しく動作することと、`test_judge` を呼び出しても `testcase` の値が変化しないことが確認できます。

```python
testcase = [
    [ 0, 1 ], # 着手順とゲーム盤
    [ 0, 0 ], # 246  xxx
    [ 1, 1 ], # 13.  oo.
    [ 1, 0 ], # 5..  o..
    [ 0, 2 ],
    [ 2, 0 ],
    Marubatsu.CROSS, # 最後の要素が、期待される処理を表す
]

print("befor test_judge:", testcase)
test_judge(testcase)
print("after test_judge:", testcase)
```
実行結果
```
befor test_judge: [[0, 1], [0, 0], [1, 1], [1, 0], [0, 2], [2, 0], 'x']
Turn o
xxx
oo.
o..

ok
after test_judge: [[0, 1], [0, 0], [1, 1], [1, 0], [0, 2], [2, 0], 'x']
```

なお、スライスに関する詳細については、下記のリンク先を参照して下さい。
:::

https://docs.python.org/ja/3/library/stdtypes.html?highlight=%E3%82%B9%E3%83%A9%E3%82%A4%E3%82%B9#common-sequence-operations

#### テストケースを表すデータ構造の修正 その2

次に、テストケースを表すデータ構造の中に、着手を表すデータと、期待される処理を表すデータを __別々に記録する__ 方法を紹介します。

ただし、そのような方法は、__既に紹介しています__ ので、新しいことを学ぶ必要はありません。どのようなデータ構造になるかについて少し考えてみて下さい。

先ほど、座標を表す 2 つのデータを、`[0, 1]` のように、list の要素として別々に代入しました。それと同様の方法で、list を使って下記のようなデータ構造で、テストケースを表すデータを記述することができます。

* list の __0 番の要素__ に、__着手を表すデータ__ を代入する
* list の __1 番の要素__ に、__期待される__ `judge` メソッドの __返り値__ の値を代入する

:::note
本記事では list を使いますが、tuple を使っても構いません。また、上記の list の要素の順番を入れ替えても構いません。
:::

下記は、上記の修正したデータ構造で `testcase` に、0 行に × が 3 つ並んだテストケースを代入するプログラムです。

```python
testcase = [
    [
        [ 0, 1 ], # 着手順とゲーム盤
        [ 0, 0 ], # 246  xxx
        [ 1, 1 ], # 13.  oo.
        [ 1, 0 ], # 5..  o..
        [ 0, 2 ],
        [ 2, 0 ],
    ],
    Marubatsu.CROSS, # 期待される judge メソッドの返り値
]
```

`test_judge` の修正は、2 行目と 4 行目を以下のように追加、修正するだけで済みます。

2 行目では、__`testdata` と `winner`__ という変数に、`testcase` の __0 番と 1 番の要素を代入__ しています。`testcase` の __中身を変更するのではなく__、`testdata` と `winner` という __異なる変数__ に `testcase` の __要素を代入__ することで、`pop` メソッドを使った場合のように、`testcase` の __中身が変化__ することは __無くなります__。

修正前のプログラムでは、`testcase` に着手するマスの座標のデータが代入されていましたが、__修正後__ は、__`testdata`__ という名前の変数に __そのデータが代入__ されるようになったので、4 行目では、__`testcase`__ を __`testdata` に修正__ しています。

```python
 1  def test_judge(testcase):
 2      testdata, winner = testcase
 3      mb = Marubatsu()
 4      for x, y in testdata:
 5          mb.move(x, y)
 6      print(mb)
 7
 8      if mb.judge() == winner:
 9          print("ok")
10      else:
11          print("error!")
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def test_judge(testcase):
    testdata, winner = testcase
    mb = Marubatsu()
    for x, y in testdata:
        mb.move(x, y)
    print(mb)
    if mb.judge() == winner:
        print("ok")
    else:
        print("error!")
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def test_judge(testcase):
-   winner = testcase.pop()
+   testdata, winner = testcase
    mb = Marubatsu()
-   for x, y in testcase:
+   for x, y in testdata:
        mb.move(x, y)
    print(mb)
    if mb.judge() == winner:
        print("ok")
    else:
        print("error!")
```
</details>

下記のプログラムの実行結果から、正しく動作することが確認できます。また、先程と異なり、`test_judge` を呼び出しても、`testcase` の値は __変化しません__。

```python
testcase = [
    [
        [ 0, 1 ], # 着手順とゲーム盤
        [ 0, 0 ], # 246  xxx
        [ 1, 1 ], # 13.  oo.
        [ 1, 0 ], # 5..  o..
        [ 0, 2 ],
        [ 2, 0 ],
    ],
    Marubatsu.CROSS, # 期待される judge メソッドの返り値
]
print("befor test_judge:", testcase)
test_judge(testcase)
print("after test_judge:", testcase)
```

```実行結果
befor test_judge [[[0, 1], [0, 0], [1, 1], [1, 0], [0, 2], [2, 0]], 'x']
Turn o
xxx
oo.
o..

ok
after test_judge [[[0, 1], [0, 0], [1, 1], [1, 0], [0, 2], [2, 0]], 'x']
```

#### list の用途と、修正その 1 を採用しない理由

__修正その 2__ のデータ構造は、list の要素の中に __異なる種類のデータが代入__ されているので、先程の修正その 1 のデータ構造と大きな違いはないのではないか？と思う人がいるかもしれませんが、この 2 つのデータ構造は、性質が __大きく異なります__。

list[^2] は主に、以下のような __2 種類の用途__ で使われます。

* 用途 1
    * __同じ種類__ の __任意の数のデータ__ を __まとめて扱う__
    * インデックスは list に代入された複数のデータの __順番__ を表す
    * for 文などの __繰り返しの処理__ の [反復可能オフジェクト](https://qiita.com/ysgeso/items/0d64d1ddba1982b5a5d3#%E5%8F%8D%E5%BE%A9%E5%8F%AF%E8%83%BD%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88) として記述することで、__先頭の要素から順番__ に取り出して __同じ種類の処理を行う__ ことができる
* 用途 2[^3]
    * __異なる種類__ の __決められた数__ のデータを __まとめて扱う__
    * インデックスは、要素に代入されたデータの __種類__ を表す
    * __一般的__ に、for 文などの 繰り返しの処理の反復可能オブジェクトとして __利用しない__

__修正前__ のテストケースを表すデータは、__着手を行う座標__ という、__同じ種類__ の __複数の__ データを要素として持つ list なので、__前者の用途__ の list です。__インデックス__ は、着手の __順番__ を表します。また、テストケースを作成する際に、__for 文の中で記述__ して利用しています。

__修正その 2__ のテストケースを表すデータは、__着手を行う座標を表すデータ__ と __期待される処理の値__ という、__異なる種類__ のデータを __まとめて扱う__ ので、__後者の用途__ の list です。__インデックス__ は、順番ではなく、__データの種類__ を表します。また、テストケースを作成する際に、テストケースを表すデータを __直接__ for 文の中で記述 __しません__。

:::note
修正その 2 の `test_judge` の中の for 文の中で記述しているのは、`testcase` __ではなく__、`testcase` の __0 番の要素__ である、`testdata` です。`testdata` は __修正前__ のテストケースを表すデータなので、__前者の用途__ の list です。
:::

:::note
__着手するマスの座標__ を表すデータを、x 座標と y 座標の値を要素とする `[1, 2]` のような list で表現しましたが、これは __後者の用途__ の list です。
:::


__修正その 1__ のテストケースを表すデータは、__前者の用途__ の list の要素に、期待される処理の値という、__異なる種類のデータ__ を __追加__ してるので、__前者の用途__ と __後者の用途__ が __混在した__ list になっています。このような list は、__インデックスの意味__ に __データの順番__ と __データの種類__ という __異なる意味が混在__ することになってしまうので、__扱いづらく__、__バグの原因__ となる __可能性が高く__ なります。また、`pop` メソッドなどを使って、__データを分離__ しなければ、for 文などの __繰り返しで利用__ することは __できません__。これが、__前者の用途__ と __前者の用途__ を __混在する__、__修正その 1__ のようなデータ構造を __使わないほうが良い__ 理由です。

### 引き分けの場合のテスト

__引き分け__ の場合も、__同様の方法__ でテストケースを用意して、`test_judge` を実行します。引き分けの場合は __すべてのマス__ を、__誰も勝利しない__ 状態で埋める必要があるので少し大変ですが、そのような状態である、下図右のテストケースでテストを行うことにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f84e9c2c-500a-5e77-9a95-5fe231890b0c.png" width="250">

下記は、上図左の順番でマークを配置するためのテストケースを表すデータを `testcase` に代入し、`test_judge` を呼び出すことでテストを行うプログラムです。実行結果から、正しい盤面が作られ、テストで期待される処理が行われることが確認できます。

```python
testcase = [
    [
        [ 0, 0 ], # 着手順とゲーム盤
        [ 0, 1 ], # 136  oox
        [ 1, 0 ], # 245  xxo
        [ 1, 1 ], # 789  oxo
        [ 2, 1 ],
        [ 2, 0 ],
        [ 0, 2 ],
        [ 1, 2 ],
        [ 2, 2 ],
    ],
    Marubatsu.DRAW, # 期待される judge メソッドの返り値
]

test_judge(testcase)
```
実行結果
```
Turn x
oox
xxo
oxo

ok
```

### 複数のテストケースをまとめたデータ構造

`test_judge` を使って 3 つのテストケース対してテストを行うためには、下記のプログラムのように、__3 種類__ のそれぞれのテストケースに対して、「`testcase` にテストケースを表すデータを __代入__ し、`test_judge` を呼び出す」という処理を __3 回記述する__ 必要があります。このような方法では、テストケースが __増えた場合__ にテストを行うのが __大変__ です。

```python
testcase = [ 略 ] # 〇 の勝利のデータを記述する
test_judge(testdata)
testcase = [ 略 ] # × の勝利のデータを記述する
test_judge(testdata)
testcase = [ 略 ] # 引き分けのデータを記述する
test_judge(testdata)
```

そこで、複数のテストケースを __1 つのデータ__ として __まとめ__、`test_judge` の中で __繰り返し__ を使って一つ一つのテストケースに対する __テストを行う__ ことにします。

__複数__ のテストケースを __1 つ__ の変数で __まとめる__ 方法として、これまでと同様に __list を利用__ します。下記のプログラムは、__3 つ__ の __テストケースを表すデータ__ を __要素として持つ list__ を `testcases` という名前の変数に代入しています。__複数形の名前__ にすることで、__中に複数__ のテストケースのデータが __代入__ されていることが __明確になる__ ようにしています。

```python
testcases = [
    # 〇 の勝利のテストケース
    [
        [
            [ 0, 0 ], # 着手順とゲーム盤
            [ 0, 1 ], # 135  ooo
            [ 1, 0 ], # 24.  xx.
            [ 1, 1 ], # ...  ...
            [ 2, 0 ],
        ],
        Marubatsu.CIRCLE, # 期待される judge メソッドの返り値
    ],
    # × の勝利のテストケース
    [
        [
            [ 0, 1 ], # 着手順とゲーム盤
            [ 0, 0 ], # 246  xxx
            [ 1, 1 ], # 13.  oo.
            [ 1, 0 ], # 5..  o..
            [ 0, 2 ],
            [ 2, 0 ],
        ],
        Marubatsu.CROSS, # 期待される judge メソッドの返り値
    ],
    # 引き分けのテストケース
    [
        [
            [ 0, 0 ], # 着手順とゲーム盤
            [ 0, 1 ], # 136  oox
            [ 1, 0 ], # 245  xxo
            [ 1, 1 ], # 789  oxo
            [ 2, 1 ],
            [ 2, 0 ],
            [ 0, 2 ],
            [ 1, 2 ],
            [ 2, 2 ],
        ],
        Marubatsu.DRAW, # 期待される judge メソッドの返り値
    ],
]
```

次に、データ構造の修正に合わせて、`test_judge` を以下のように修正します。

* __仮引数の名前__ を `testcase` から `testcases` に __修正__ する
* 2 行目に、__`testcases`__ の先頭から順番に __要素を取り出して__、__`testcase` に代入__ する __for 文__ を記述する
* 元の `test_judge` のブロックの内容に __インデントを追加__ して、2 行目の __for 文のブロックの処理__ になるように修正する

```python
 1  def test_judge(testcases):
 2      for testcase in testcases:
 3          testdata, winner = testcase
 4          mb = Marubatsu()
 5          for x, y in testdata:
 6              mb.move(x, y)
 7          print(mb)
 8
 9          if mb.judge() == winner:
10              print("ok")
11          else:
12              print("error!")
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for x, y in testdata:
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

<details><summary><strong>修正箇所</strong></summary>

インデントの修正部分は省略します。

```diff_python
- def test_judge(testcase):
+ def test_judge(testcases):
+   for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for x, y in testdata:
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

下記のプログラムは、`testcases` に対して、修正した `test_judge` でテストを行っています。実行結果から、__3 つのテストケース__ に対して __まとめて__ 正しく __テストが行われる__ ことが確認できます。

```python
test_judge(testcases)
```
実行結果
```
Turn x
ooo
xx.
...

ok
Turn o
xxx
oo.
o..

ok
Turn x
oox
xxo
oxo

ok
```

これで、命令網羅（C0）のテストが無事完了しました。

## 分岐網羅(C1) によるテスト

[分岐網羅(C1)](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E5%88%86%E5%B2%90%E7%B6%B2%E7%BE%85decision-coverage) によるテストは、__すべて__ の条件分岐の __条件式が `True` と `False` で実行__ されるようなテストです。

下記の表は、先程の、命令網羅(C0) によるテストケースに対して、それぞれの条件分岐の条件式がどのような計算結果になるかを表します。

| テストケース | 1 つめの条件式 | 2 つめの条件式 | 3 つめの条件式 |
|:-:|:-:|:-:|:-:|
| 〇 の勝利のテストケース  | `True`  | `False`  | `False` |
| × の勝利のテストケース  | `False`  | `True`  | `False` |
| 引き分けのテストケース  | `False`  | `False`  | `True` |

表から、先程命令網羅(C0) で行ったテストは、__分岐網羅(C1) によるテストの条件を満たしている__ ことがわかるので、新しくテストを行う __必要はありません__。また、そのことは、下図の __命令網羅(C0) のフローチャートの図で__、__すべての線__ がいずれかのテストケースで __実行されている__ ことでも確認することが出来ます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ecf61020-a308-3bba-5dce-0f174d1881d3.png" width="500">

なお、今回の例では、__たまたま__ 命令網羅(C0) で行ったテストが分岐網羅(C1) によるテストの条件を満たしていますが、分岐網羅(C1) の方が __条件が厳しい__ ので、__一般的__ には命令網羅(C0) より __多くのテストケース__ でテストを行う __必要があります__。

### 条件網羅(C2) によるテスト

以前の記事で説明したように、[条件網羅(C2)](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E6%9D%A1%E4%BB%B6%E7%B6%B2%E7%BE%85condition-coverage) によるテストは、命令網羅(C0) や 分岐網羅(C1) の条件を __満たさない場合がある__ ので、__個人的__ には __中途半端な精度のテスト__ だと __思います__。そのため、本記事では条件網羅(C2) によるテストは行いません。

### 判定条件/条件網羅(CDC) によるテスト

以前の記事で説明したように、[判定条件/条件網羅(CDC)](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E5%88%A4%E5%AE%9A%E6%9D%A1%E4%BB%B6%E6%9D%A1%E4%BB%B6%E7%B6%B2%E7%BE%85-condition--decision-coverage) によるテストは、__式の値__ と、__条件式の値__ の __因果関係__ を __完全にチェックできない__ 可能性があるという __欠点__ があります。そのため、本記事では、判定条件/条件網羅(CDC) によるテストは行いません。

### 複数条件網羅(MCC) によるテスト

[複数条件網羅(MCC)](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E8%A4%87%E6%95%B0%E6%9D%A1%E4%BB%B6%E7%B6%B2%E7%BE%85multiple-condition-coverage) によるテストは、条件分岐の __条件式の中__ に、__and または or__ の __論理演算子__ が記述されている場合、それらで連結された __それぞれの式__ が __`True` と `False`__ の __すべての組み合わせ__ で __1 度は実行__ されるようなテストです

`judge` メソッドの __1 つ目の条件式__ では、下記のプログラムのように、__8 つの 式__ が or 演算子によって連結されています。このような場合に複数条件網羅(MCC) によるテストを行うと、$2^8 = $ __256 個__ ものテストケースを用意する必要があります。これはさすがに __大変すぎる__ ので、本記事では複数条件網羅(MCC) によるテストは行いません。

```python
    # 〇 の勝利の判定
    if self.board[0][0] == self.board[1][0] == self.board[2][0] == Marubatsu.CIRCLE or \
       self.board[0][1] == self.board[1][1] == self.board[2][1] == Marubatsu.CIRCLE or \
       self.board[0][2] == self.board[1][2] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[0][0] == self.board[0][1] == self.board[0][2] == Marubatsu.CIRCLE or \
       self.board[1][0] == self.board[1][1] == self.board[1][2] == Marubatsu.CIRCLE or \
       self.board[2][0] == self.board[2][1] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[0][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CIRCLE or \
       self.board[2][0] == self.board[1][1] == self.board[0][2] == Marubatsu.CIRCLE:
        self.winner = Marubatsu.CIRCLE
```

# 複雑なデータ構造に対する関数のヒント

テストケースを表すデータのような、__複雑なデータ構造__ のデータに対する [関数の引数と返り値のデータ型のヒント](https://qiita.com/ysgeso/items/4d55e73d6571fe85aa49#%E9%96%A2%E6%95%B0%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%A8%E8%BF%94%E3%82%8A%E5%80%A4%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B%E3%81%AE%E3%83%92%E3%83%B3%E3%83%88%E3%81%AE%E8%A1%A8%E8%A8%98) は __簡潔に記述__ することが __難しいの__ で、そのような場合は __関数のヒント__ には __何も記述せず__、下記のリンク先のように、__[docstring](https://qiita.com/ysgeso/items/4d55e73d6571fe85aa49#docstring)__ のほうにデータ構造を __記述__ したほうが良いでしょう。

https://github.com/ysgeso/marubatsu/blob/master/026/test_new.py

# 今回の記事のまとめ

記事が長くなったので、今回の記事はここまでにします。

[以前の記事](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` メソッドには __いくつかのバグが存在__ します。今回の記事では、`judge` メソッドに対して、__命令網羅__(C0) と __分岐網羅__(C1) の条件を満たすテストを行いましたが、それらのテストでは __バグを発見__ することは __できませんでした__。

これは、`judge` メソッドのような、__and や or 演算子__ を使った複雑な __条件式が記述__ されている __関数のテスト__ を行う場合は、命令網羅(C0) や分岐網羅(C1) のテストでは __不十分__ な __場合がある__ こと原因です。次回以降の記事では、改良条件判断網羅(MC/DC) によるテストなどを使って、バグを発見することが出来ることを示します。

なお、このことから、命令網羅(C0) や分岐網羅(C1) のテストが無意味であるということは __決してありません__。例えば、条件式の中で、__and や or 演算子が使われていなければ__ 条件網羅に関するテストを行うことはそもそも __不可能です__。

どのテストを行うかは、テストする __関数の性質__、求められる __テストの精度__、__費やすことが出来る時間__ などから、__自分で判断して選択__ する必要があるということです。

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

以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。

https://github.com/ysgeso/marubatsu/blob/master/026/marubatsu.ipynb

今回の記事では、marubatsu.py は更新していないので、marubatsu_new.py はありません。

以下のリンクは、今回の記事で更新した test_new.py です。

https://github.com/ysgeso/marubatsu/blob/master/026/test_new.py

# 次回の記事

https://qiita.com/ysgeso/items/51c2eba867aa560808c6

[^1]:pop には「__飛び出る__」という意味があり、list の要素が外に飛び出て削除されることからそのような名前が付けられたという説があります
[^2]:tuple の用途も同様です
[^3]:この用途で、扱う __データの種類が多い__ 場合は、__dict__ を使ったほうが __わかりやすくなります__。そのような dict の使用方法については、次回の記事で紹介します