Pythonで〇×ゲームのAIを一から作成する　その25　テストを行う関数とデータと処理の分離

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/728cee8982fe0ed9da38

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

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

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

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

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

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

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

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

## 前回までのおさらい

前回の記事では、テストの手法について説明しました。今回の記事では、それらの手法を使って `judge` メソッドのテストを行います。

:::note
python には、unittest や pytest など、__テストを行うためのモジュール__ がいくつかあります（下記のリンク先を参照）。また、VSCode にもテストを行うための機能が用意されています。本記事では、テストで行う __処理の具体的な中身__ を __理解してもらいたかった__ ので、それらのモジュールや機能を __利用せずにテストを行う__ ことにします。もちろん、それらのモジュールはテストを行う際に便利なので、今回の記事を読んで、テストに興味を持った方は勉強して使ってみて下さい。
:::

https://docs.python.org/ja/3/library/unittest.html?highlight=unittest#module-unittest

https://docs.pytest.org/en/7.4.x/
# `judge` メソッドのテスト

今回の記事では、`judge` メソッドのテストの実施方法を、具体例に挙げて説明します。

その際に、__データと処理の分離__ という、今後のプログラムでも何度も利用する __重要な考え方__ について説明します。

## `judge` メソッドの定義

下記のプログラムは、前々回の記事で定義した `judge` メソッドの定義です。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = None
    # 〇 の勝利の判定
    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:
        winner = Marubatsu.CIRCLE
        
    # × の勝利の判定
    if self.board[0][0] == self.board[1][0] == self.board[2][0] == Marubatsu.CROSS or \
       self.board[0][1] == self.board[1][1] == self.board[2][1] == Marubatsu.CROSS or \
       self.board[0][2] == self.board[1][2] == self.board[2][2] == Marubatsu.CROSS or \
       self.board[0][0] == self.board[0][1] == self.board[0][2] == Marubatsu.CROSS or \
       self.board[1][0] == self.board[1][1] == self.board[1][2] == Marubatsu.CROSS or \
       self.board[2][0] == self.board[2][1] == self.board[2][2] == Marubatsu.CROSS or \
       self.board[0][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CROSS or \
       self.board[2][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CROSS:
        winner = Marubatsu.CROSS     

    # 引き分けの判定
    if not(self.board[0][0] == Marubatsu.EMPTY or \
           self.board[1][0] == Marubatsu.EMPTY or \
           self.board[2][0] == Marubatsu.EMPTY or \
           self.board[0][1] == Marubatsu.EMPTY or \
           self.board[1][1] == Marubatsu.EMPTY or \
           self.board[1][1] == Marubatsu.EMPTY or \
           self.board[0][2] == Marubatsu.EMPTY or \
           self.board[1][2] == Marubatsu.EMPTY or \
           self.board[2][2] == Marubatsu.EMPTY):
        winner = Marubatsu.DRAW

    # winner を返り値として返す
    return winner
```

## `judge` メソッドのフローチャート

下図は、`judge` メソッドのフローチャートです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b8309a30-998d-df7d-05d1-4970764c824c.png" width="600">

## 制御フローテストの一覧

下記の表は、前回の記事で紹介した、制御フローテストの一覧を再掲したものです。

今回の記事では、この中から、__命令網羅__(C0) の前半を、次回以降の記事で __命令網羅__(C0) の後半、__分岐網羅__(C1)、__改良条件判断網羅__(MC/DC)、__経路組み合わせ網羅__ で `judge` メソッドのテストを行うことにします。本記事では、他のテストは行いませんが、その理由については記事の中で説明します。

| 名称 | 略称 | テストケースの数 | 精度 |
|:-:|:-:|:-:|:-:|
| [命令網羅](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E5%91%BD%E4%BB%A4%E7%B6%B2%E7%BE%85statement-coverage)  | C0、SC  | 最小  | 最小 |
| [分岐網羅](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E5%88%86%E5%B2%90%E7%B6%B2%E7%BE%85decision-coverage)  | C1、DC  | 小  | 小  |
| [条件網羅](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E6%9D%A1%E4%BB%B6%E7%B6%B2%E7%BE%85condition-coverage)  | C2、CC  | 小~中  | 小  |
| [判定条件/条件網羅](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  | 小~中  | 中  |
| [複数条件網羅](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)  | MCC  | 大  | 大  |
| [改良条件判断網羅](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E6%94%B9%E8%89%AF%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD%E7%B6%B2%E7%BE%85modified-condition--dicision-coverage)  | MC/DC  | 中  | 大  |
| [経路組み合わせ網羅](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E7%B5%8C%E8%B7%AF%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E7%B6%B2%E7%BE%85path-coverage)  | なし  | 最大  | 最大  |

## 命令網羅(C0) によるテスト

記述が長くなるので、以後は __テストで利用する__「特定のマスにマークが配置された `Marubastu` クラスのインスタンス」のことを、単に __テストケース__ と表記します。

[命令網羅(C0)](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E5%91%BD%E4%BB%A4%E7%B6%B2%E7%BE%85statement-coverage) によるテストを行うためには、__すべて__ の __条件分岐と文__ が 1 度は __実行される__ ようなテストケースを用意する必要があります。フローチャートでは、__すべて__ の長方形とひし形で表される __図形の処理__ が 1 度は __実行される__ テストケースが必要となります。

`judge` メソッドには if 文が 3 つあり、いずれも  __`else` が記述されていない__ ので、__それぞれの条件文が `True` になる__ ようなテストケースを用意する必要があります。先ほどのフローチャートを見て、そのことを確認して下さい。

3 つの if 文の条件式が __それぞれ `True` になる条件__ と、その場合に `judge` メソッドを呼び出した際に __期待される処理__ は以下の表のようになります。〇×ゲームの性質上、「〇が勝利した場合」と「×が勝利した場合」と「引き分け」が __同時に起きることは無い__ ので、__3 つ__ のテストケースが __必要__ になることがわかります。

:::note
下記の表の期待される処理に __`None` が存在しない__ ことからわかるように、命令網羅(C0) では、__決着がついていない__ テストケースを __用意する必要がありません__。従って、命令網羅(C0) では、決着がついていない状態のテストは __行われません__。
:::

| if 文 | 条件式が `True` になる条件 | 期待される処理（返り値） |
|:-:|:-:|:-:|
| 1 つ目  | 〇 が勝利した場合  | `Marubatsu.CIRCLE` |
| 2 つ目  | × が勝利した場合  | `Marubatsu.CROSS` |
| 3 つ目  | 引き分けの場合  | `Marubatsu.DRAW` |

:::note warn
プログラミングにある程度慣れている方であれば、上記の説明を見て __違和感__ を覚えた人がいるのではないかと思います。実際に、上記の説明の __一部は間違っています__ が、その点については __勘違いしやすい間違いの実例__ として、__今後のテストで発見する__ ことにしますので、__間違いがあるまま説明を続行__ します。余裕がある方は、何が間違っているかについて考えてみて下さい。
:::

下図は、〇 が勝利した場合、× が勝利した場合、引き分けの場合の 3 つのケースでテストを行った際に行われる処理を、フローチャートで表現した図です。図から、この 3 つのテストケースによって、__すべて__ の __条件分岐と処理__ を表す図形が __実行される__ ので、__命令網羅(C0) の条件を満たす__ テストが行われることが確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9b0f6460-a37a-6ed7-2fdf-388fec8ca6f7.png" width="600">

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

まず、__〇 が勝利した場合__ のテストケースを考えます。命令網羅(C0) では、__〇 が勝利__ した場合であれば、__どのようなゲーム盤でも構わない__ ので、本記事では __0 行に 〇 が 3 つ並んだ__ ゲーム盤を表すテストケースを用意することにします。もちろん、〇が勝利した場合の __他のテストケース__ を用意しても __構いません__。

[前々回の記事](https://qiita.com/ysgeso/items/1fdd19037bbf64082a42#%E5%AE%9F%E8%A3%85%E3%81%AE%E7%A2%BA%E8%AA%8D) では、`judge` メソッドの処理を確認する際に、`place_mark` メソッドを使って、ゲーム盤に __〇 だけを配置__ した状態で確認を行いました。そのような、__実際の__ 〇×ゲームを遊ぶ __手順と異なる方法__ でマークを配置するようなテストケースは、__手抜き__ を行っているので __用意するのが簡単__ である __反面__、__実際__ の 〇×ゲームでは __起きない状況__ に対するテストになるので、テストの __精度が低くなる__ という問題があります。

そこで、今回は、__着手__ を行う `move` メソッドを使って、__実際に__ 〇×ゲームを __遊ぶ場合と同じ__ の手順で、__〇 と × を交互に配置__ することで、0 行に 〇 が 3 つ並んだゲーム盤を表すテストケースを用意することにします。

下記のプログラムは、下図の __左の順番__ で __着手__ を行うことで、下図の __右の 〇 が勝利した場合__ のテストケースを `mb1` に代入しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9340c73d-9e35-0dd2-b4c1-50f7a3b2732e.png" width="250">

テストケースでマークが __正しく配置__ されているかどうかを __確認__ するために、9 行目でゲーム盤を表示しています。テストケースが __間違っている__ 場合は、__正しいテスト__ を行うことが __出来ない__ ので、__テストケースの確認__ も、テストにおける __重要な作業__ です。

実行結果から、正しいマスにマークが配置されていることが確認できます。

```python
1  from marubatsu import Marubatsu
2
3  mb1 = Marubatsu()
4  mb1.move(0, 0)
5  mb1.move(0, 1)
6  mb1.move(1, 0)
7  mb1.move(1, 1)
8  mb1.move(2, 0)
9  print(mb1)
```

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

```python
from marubatsu import Marubatsu

mb1 = Marubatsu()
mb1.move(0, 0)
mb1.move(0, 1)
mb1.move(1, 0)
mb1.move(1, 1)
mb1.move(2, 0)
print(mb1)
```
</details>

実行結果
```
Turn x
ooo
xx.
...
```

下記は、このテストケースを使って `judge` メソッドのテストを行うプログラムです。1 行目の if 文の __条件式で `judge` メソッドを呼び出し__、__返り値__ が __〇 の勝利__ を表す`Marubatsu.CIRCLE` __であれば__ 2 行目で __`"ok"`__ を、__そうでなければ__ 4 行目で __`"error!"`__ を表示しています。実行結果から、この __テストケースに対して__ `judge` メソッドが __期待される処理を行う__ ことを __確認__ することが出来ます。

```python
if mb1.judge() == Marubatsu.CIRCLE:
    print("ok")
else:
    print("error!")
```
実行結果
```
ok
```

:::note
[前回の記事](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E3%83%86%E3%82%B9%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86) で、期待される処理が __行われていた場合__ は __何も表示しない__ 場合が多いと説明しましたが、今回は 1 つのテストケースに対してのみテストを行っているので __大量の表示__ が __行われることは無い__ 点と、何も表示しないと __何が起きたかがわかりづらい__ ので、`"ok"` を表示することにしました。
:::

### テストを行うための関数の定義

次に、× の勝利の場合のテストを行う必要がありますが、テストを行う際に、__毎回__ 上記のような __長いプログラムを記述__ するのは __大変__ です。そこで、今後のテストを __効率よく行う__ ことが出来るようにするために、__テストを行うための関数を定義__ することにします。

#### 関数の仕様

テストを行うための関数を定義するためには、__関数の名前__、__入力__、__処理__、__出力__ の __仕様を決める__ 必要があり、本記事では、それぞれを以下のように決めることにします。

* __関数の名前__：`judge` メソッドのテスト（test）を行うので、__`test_judge`__ とする

* __入力__：テストを行うためには、__テストケースを表すデータ__ が必要なので、仮引数 __`testcase`__ にそのデータを代入することにする

* __処理__：`testcase` に代入されたデータを使って __テストを行い__、その __結果を表示__ する

* __出力__：上記の処理によって、__画面に結果が表示__ されるので、関数の返り値としての出力は __必要がない__。従って、__返り値を返さない関数__ とする

具体的には、下記のような関数を作成することにします。この関数のブロックにどのようなプログラムを記述すれば良いかについて少し考えてみて下さい。

```python
def test_judge(testcase):
    テストケースを使ってテストを行い結果を表示する
```

#### データと処理の分離

`test_judge` を、下記のようなプログラムで定義する事を思い浮かべた人が多いのではないでしょうか？下記のプログラムは、__テストケースを直接__ `test_judge` の仮引数に __代入__ してテストを行う関数です。

この関数のブロックの中のプログラムは、__先ほどのテストを行う処理__ の、`mb1.judge() == Marubatsu.CIRCLE` を、`testcase.judge() == Marubatsu.CIRCLE` に修正したものです。

```python
def test_judge(testcase):
    if testcase.judge() == Marubatsu.CIRCLE:
        print("ok")
    else:
        print("error!")    
```

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

インデントは修正箇所に含みません。

```diff_python
def test_judge(testcase):
-   if mb1.judge() == Marubatsu.CIRCLE:
+   if testcase.judge() == Marubatsu.CIRCLE:
        print("ok")
    else:
        print("error!")    
```
</details>

この `test_judge` を使って、下記のプログラムのように、__実際__ に 0 行に 〇 が 3 つ並んだテストケースの __テストを行う__ ことができます。

```python
mb1 = Marubatsu()
mb1.move(0, 0)
mb1.move(0, 1)
mb1.move(1, 0)
mb1.move(1, 1)
mb1.move(2, 0)

test_judge(mb1)
```
実行結果
```
ok
```

上記の `test_judge` を利用することで、テストケースごとに、__テストを行う処理__ を毎回 __記述する__ 必要は __なくなります__ が、あいかわらず、__特定のマスにマークが配置__ されたテストケースを __作成する__ ための __プログラムを記述__ する __必要がある__ 点が __改善されていません__。

この問題の原因は、テストケースを作成するための __プログラムの中__ に、__直接__ マークを配置するために必要な __データが記述__ されている点です。プログラムに記述された __データ__ と __処理__ を __分離__ することで、この問題を __改善する__ ことが出来ます。

具体的には、__テストケースを作成__ するために __必要なデータ__ を記述し、`test_judge` の中で、__そのデータを使って__ テストケースを __作成する処理__ を記述します。そのためには、テストケースを __作成__ するために __必要なデータ__ の __データ構造__ を __決める__ 必要があります。

表記が長いので、以後は「テストケースを作成するために必要なデータ」のことを、__テストケースを表すデータ__ のように表記します。

言葉の説明だけではわかりづらいと思いますので、具体例を挙げて説明します。

#### データと処理を分離することの利点

先程は、下記のようなプログラムを記述して、0 行に 3 つの 〇 が配置された `Marubatsu` クラスのインスタンスをテストケースとして作成しました。

```python
1  mb1 = Marubatsu()
2  mb1.move(0, 0)
3  mb1.move(0, 1)
4  mb1.move(1, 0)
5  mb1.move(1, 1)
6  mb1.move(2, 0)
```

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

```python
mb1 = Marubatsu()
mb1.move(0, 0)
mb1.move(0, 1)
mb1.move(1, 0)
mb1.move(1, 1)
mb1.move(2, 0)
```
</details>

上記のプログラムの 2 ~ 6 行目の `move` メソッドの __実引数__ として記述された数値が、0 行 に 3 つの 〇 が配置された __テストケースを表すデータ__ です。上記のプログラムから、テストケースを表すデータを __分離__ するために、`move` メソッドに記述する __実引数を表すデータ__ を、__変数に代入して記述する__ ことにします。

`move` メソッドで着手を行う際に __必要となるデータ__ は、__マスの x, y 座標__ です。そのような 2 つのデータは、list、tuple、dict などを使って表現できますが、__簡潔に表現__ するのであれば、__list か tuple__ を使うと良いでしょう。本記事では __list を使って__、下記のプログラムのように、__マスの x 座標__、__マスの y 座標__ の順番でマスの座標を表すデータを __5 つの変数__ で記述することにします。tuple や dict を使いたい人は変更してもかまいません。

```python
data1 = [ 0, 0 ]
data2 = [ 0, 1 ]
data3 = [ 1, 0 ]
data4 = [ 1, 1 ]
data5 = [ 2, 0 ]
```
:::note
tuple を利用する場合は、`(0, 0)`、dict を利用する場合は、`{ "x": 0, "y": 0 }` のようにデータを記述します。dict を使うと __記述が長くなる__ という __欠点__ がありますが、__キー__ に `"x"` のような __文字列を使う__ ことで、list や tuple の場合と比べてdict の __値の意味がわかりやすくなる__ という利点があります。dict を使った具体例については、次回以降の記事で紹介します。
:::

テストケースを表すデータを分離することが出来たので、次は、__分離したデータ__ を使って、テストケースを __作成する処理__ を __記述__ することにします。

着手するマスの __x 座標__ は、list の __0 番 の要素__、__y 座標__ は list の __1 番の座標__ に代入されているので、5 つのマスに着手するプログラムを下記のように記述することが出来ます。

実行結果から、正しいマスにマークが配置されていることが確認できます。

```python
1  mb1 = Marubatsu()
2  mb1.move(data1[0], data1[1])
3  mb1.move(data2[0], data2[1])
4  mb1.move(data3[0], data3[1])
5  mb1.move(data4[0], data4[1])
6  mb1.move(data5[0], data5[1])
7  print(mb1)
```

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

```python
mb1 = Marubatsu()
mb1.move(data1[0], data1[1])
mb1.move(data2[0], data2[1])
mb1.move(data3[0], data3[1])
mb1.move(data4[0], data4[1])
mb1.move(data5[0], data5[1])
print(mb1)
```
</details>

実行結果
```
Turn x
ooo
xx.
...
```

元のプログラムでは、__テストケースを表すデータ__ がプログラム内に __直接記述__ されていたため、__常に__ 0 行に 〇 が 3 つ配置されたテストケースが作成されることになります。

一方、上記のプログラムには、__具体的なデータ__ が __記述されていない__ ので、下記のプログラムのように、`data1` などに代入する __値を変える__ ことで、同一のプログラムを使って、__異なる__ テストケースを __作成__ することが出来るようになります。

下記のプログラムでは、__2 行__ に 〇 が 3 つ配置されたテストケースを作成するプログラムですが、先程と比べて、1、3、5 行目の __テストケースを表すデータ__ は __変化__ していますが、9 ~ 14 行目の __インスタンスを作成する処理__ の部分は __全く__ 変化 __していません__。

```python
 1  # data1、data3、data5 が変化している
 2  data1 = [ 0, 2 ] 
 3  data2 = [ 0, 1 ]
 4  data3 = [ 1, 2 ]
 5  data4 = [ 1, 1 ]
 6  data5 = [ 2, 2 ]
 7
 8  # テストケースを作成する処理は先ほどと全く同じ
 9  mb1 = Marubatsu()
10  mb1.move(data1[0], data1[1])
11  mb1.move(data2[0], data2[1])
12  mb1.move(data3[0], data3[1])
13  mb1.move(data4[0], data4[1])
14  mb1.move(data5[0], data5[1])
15  print(mb1)
```

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

```python
# data1、data3、data5 が変化している
data1 = [ 0, 2 ] 
data2 = [ 0, 1 ]
data3 = [ 1, 2 ]
data4 = [ 1, 1 ]
data5 = [ 2, 2 ]

# テストケースを作成する処理は先ほどと同じ
mb1 = Marubatsu()
mb1.move(data1[0], data1[1])
mb1.move(data2[0], data2[1])
mb1.move(data3[0], data3[1])
mb1.move(data4[0], data4[1])
mb1.move(data5[0], data5[1])
print(mb1)
```
</details>

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

```diff_python
- data1 = [ 0, 0 ] 
+ data1 = [ 0, 2 ] 
data2 = [ 0, 1 ]
- data3 = [ 1, 0 ]
+ data3 = [ 1, 2 ]
data4 = [ 1, 1 ]
- data5 = [ 2, 0 ]
+ data5 = [ 2, 2 ]

mb1 = Marubatsu()
mb1.move(data1[0], data1[1])
mb1.move(data2[0], data2[1])
mb1.move(data3[0], data3[1])
mb1.move(data4[0], data4[1])
mb1.move(data5[0], data5[1])
print(mb1)
```
</details>


実行結果
```
Turn x
...
xx.
ooo
```

:::note warn
__データと処理を分離__ することで、__分離したデータ__ の __内容を変化__ させることで、__同じプログラム__ で __異なる__ テストケースを __作成できる__ ようになります。
:::

この話を聞いた人は、__関数__ や、__繰り返し__ などでも __同様__ に __データと処理の分離__ を行っているのではないかと思った方がいるかもしれませんが、__その通り__ です。関数の場合は、__仮引数__ が __データ__、__関数のブロック__ が __処理__ という形でデータと処理の分離を行っています。データと処理を分離するという __考え方__ は、過去の記事で既に __紹介済__ ということです。

実際に、この後で、__テストケースを表すデータ__ を `test_judge` の __仮引数に代入__ し、`test_judge` の __ブロックの中__ で、そのデータを使ってテストケースを __作成する処理を記述__ するようにプログラムを修正します。

#### テストケースを表すデータのデータ構造

先程の、テストケースを表すデータを、__5 つの変数__ に分けて __代入する__ というデータ構造には、__5 つ__ の着手を行う座標を表す __データしか表現できない__ という欠点があります。また、着手を表す __データごと__ に __1 つの変数を用意__ して代入するのは __面倒__ です。

__着手__ 行う座標を表す __複数のデータ__ は、__list や tuple__ を使うことで __1 つ__ の変数に __まとめて代入__ することが出来ます。list と tuple の __どちらを使っても構いません__ が、先程は 2 つの座標を表すデータを list で表現したので、今回も __list を使うことにします__。

下記のプログラムは、__5 つ__ の着手を行う座標を表す __データ__ を、__1 つの list__ で __まとめた__ ものを、`testcase` という変数に代入するプログラムです。

```python
data1 = [ 0, 0 ]
data2 = [ 0, 1 ]
data3 = [ 1, 0 ]
data4 = [ 1, 1 ]
data5 = [ 2, 0 ]
testcase = [ data1, data2, data3, data4, data5 ]
```

上記のプログラムは、5 つの座標を表すデータを、一旦変数に代入していましたが、下記のプログラムのように、それぞれのデータを __直接 list の要素として記述する__ ことで、__1 つの変数だけ__ を使って __データを代入__ するが出来ます。また、`testcase` に代入されるデータは、__2 次元配列__ を表す list になります。

なお、2 次元配列を表す list は __意味が分かりづらい__ ので、下記のプログラムでは、__コメント__ で `testcase` が __どのようなデータ__ を表すかを __示す__ という __工夫__ を行っています。

```python
testcase = [
    [ 0, 0 ], # 着手順とゲーム盤
    [ 0, 1 ], # 135  ooo
    [ 1, 0 ], # 24.  xx.
    [ 1, 1 ], # ...  x..
    [ 2, 0 ],
]
```

:::note
どこかで聞いたような話だと思った方がいるかもしれませんが、実際に同様の説明を [過去の記事](https://qiita.com/ysgeso/items/9efd60c7a28381e6cbf7#2-%E6%AC%A1%E5%85%83%E9%85%8D%E5%88%97%E3%82%92%E8%A1%A8%E3%81%99-list-%E3%82%92%E7%9B%B4%E6%8E%A5%E8%A8%98%E8%BF%B0%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95) で行っています。かなり前の記事だったので、もう一度同じ説明をしましたが、__以後は__ このような場合の __説明を省略__ することにします。
:::

この `testcase` を使って 5 つのマスに着手を行うプログラムは以下のようになります。意味が分かりづらいと思った方は、__`testcase[0]`__ には __`data1`__ と __同じデータが代入__ されているとを思い出してください。従って、__`testcase[0][0]`__ と __`data1[0]`__ には __同じデータが代入__ されています。下記の修正箇所を見て、そのことを確認して下さい。


実行結果から、正しいマスにマークが配置されていることが確認できます。

```python
mb1 = Marubatsu()
mb1.move(testcase[0][0], testcase[0][1])
mb1.move(testcase[1][0], testcase[1][1])
mb1.move(testcase[2][0], testcase[2][1])
mb1.move(testcase[3][0], testcase[3][1])
mb1.move(testcase[4][0], testcase[4][1])
print(mb1)
```

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

```diff_python
mb1 = Marubatsu()
- mb1.move(testcase[0][0], testcase[0][1])
+ mb1.move(data1[0], data1[1])
- mb1.move(testcase[1][0], testcase[1][1])
+ mb1.move(data2[0], data2[1])
- mb1.move(testcase[2][0], testcase[2][1])
+ mb1.move(data3[0], data3[1])
- mb1.move(testcase[3][0], testcase[3][1])
+ mb1.move(data4[0], data4[1])
- mb1.move(testcase[4][0], testcase[4][1])
+ mb1.move(data5[0], data5[1])
print(mb1)
```
</details>


実行結果
```
Turn x
ooo
xx.
...
```

これで、0 行に 3 つの 〇 が配置されたテストケースを表すデータを記述して、`testcase` という __1 つの変数__ に __代入__ することができました。

:::note warn
__テストケースを表すデータ__ の __データ構造__ は、以下のようになります。

* 着手する __マスの座標__ を表すデータを __要素__ とする __list で表現__ する
* list の __要素の順番__ は、__着手__ を行う __順番に対応__ する
* __マスの座標__ は、マスの __x 座標__ と __y 座標__ を __要素__ とする __list で表現__ する
:::

上記のように、テストケースを表すデータは、list の要素の中に list が __入れ子で代入__ されるので __2 次元配列__ を表す __list__ になります。また、__1 つ目__ のインデックスが __着手の順番__ を、__2 つ目__ のインデックスが __x 座標 または y 座標__ を表す __数値__ になります。

#### テストケースを表すデータを使ってテストケースを作成するプログラム

先程の `testcase` のデータからテストケースを作成する下記のプログラムは、__5 つ__ の着手を行うプログラムですが、`testcase` のデータ内に記録された __着手の数__ は __5 つ__ である __とは限りません__。従って、下記のプログラムは、__5 つ以外__ の着手が __記録__ された `testcase` のデータに対して __利用する__ ことは __できません__。そのため、__任意の数__ の着手が記録された `testcase` にからテストケースを作成するプログラムを記述する必要があります。

```python
mb1 = Marubatsu()
mb1.move(testcase[0][0], testcase[0][1])
mb1.move(testcase[1][0], testcase[1][1])
mb1.move(testcase[2][0], testcase[2][1])
mb1.move(testcase[3][0], testcase[3][1])
mb1.move(testcase[4][0], testcase[4][1])
print(mb1)
```

`testcase` を使ってテストケースを作成するプログラムは、`testcast` に代入された __list__ の __先頭の要素__ が表す座標 __から順番__ に着手を行う処理を行います。従って、下記のプログラムのように、__for 文__ を使った __繰り返しで__ 記述することができます。

2 行目の for 文によって、繰り返しのたびに `testcase` に代入された __list の先頭の要素__ から __順番に__ `data` という __変数に代入__ されて、3 行目の for 文の __ブロックの処理__ が行われます。`data` に代入された __list の要素__ には、着手を行うマスの __x 座標と y 座標__ を表すデータが代入されているので、3 行目のように記述することで、`testcase` に記録された __着手を行うマスの座標__ を表すデータを __先頭から順番__ に使って __着手__ が行われます。

実行結果から、正しいマスにマークが配置されていることが確認できます。

```python
mb1 = Marubatsu()
for data in testcase:
    mb1.move(data[0], data[1])
print(mb1)
```

実行結果
```
Turn x
ooo
xx.
...
```

#### プログラムをわかりやすくする工夫

上記のプログラムの 3 行目の `move` メソッドの実引数に記述した `data[0]` と `data[1]` は、それらが具体的に何のデータを表しているかが __わかりづらい__ という欠点があります。そこで、それらを __わかりやすい名前の変数に代入__ することでプログラムを __わかりやすくする__ という __工夫__ を行うことにします。このような修正を行うのは、プログラムを __わかりやすく記述する__ ことで、プログラムの __バグの発生を減らしたり__、バグが発生した場合でも __バグの原因を見つけやすくなる__ という __利点__ が得られるかです。

`data[0]` と `data[1]` は、それぞれ __x 座標__ と __y 座標__ を表すので、下記のプログラムのように、__`x` と `y`__ という変数にそれぞれの値を代入するように修正します。修正前のプログラムと見比べて、見た目が __わかりやすくなっている__ ことを確認して下さい。

実行結果から、正しいマスにマークが配置されていることが確認できます。

```python
mb1 = Marubatsu()
for data in testcase:
    x = data[0]
    y = data[1]
    mb1.move(x, y)
print(mb1)
```

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

```diff_python
mb1 = Marubatsu()
for data in testcase:
+   x = data[0]
+   y = data[1]
-   mb1.move(data[0], data[1])
+   mb1.move(x, y)
print(mb1)

```
</details>


実行結果
```
Turn x
ooo
xx.
...
```

[以前の記事](https://qiita.com/ysgeso/items/44884dcf168aab949508#for-%E6%96%87%E3%81%A7-dict-%E3%81%AE%E3%82%AD%E3%83%BC%E3%81%A8%E5%80%A4%E3%81%AE%E4%B8%A1%E6%96%B9%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%97) で、tuple の要素を __個別の変数に代入する__ 処理を紹介しましたが、__list の要素__ に対しても 3 行目のように、__同様の処理を記述__ することができます。下記のプログラムは、先程のプログラムの3、4 行目の処理を、1 行のプログラムでまとめたものです。

実行結果から、正しいマスにマークが配置されていることが確認できます。

```python
mb1 = Marubatsu()
for data in testcase:
    x, y = data
    mb1.move(x, y)
print(mb1)
```

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

```diff_python
mb1 = Marubatsu()
for data in testcase:
-   x = data[0]
-   y = data[1]
+    x, y = data
    mb1.move(x, y)
print(mb1)

```
</details>


実行結果
```
Turn x
ooo
xx.
...
```

上記のプログラムは、for 文の __変数の所__ に `x, y` を __直接記述__ することで、下記のプログラムのように、__さらに簡潔に記述__ することが出来ます。

実行結果から正しいマスにマークが配置されていることが確認できます。

```python
mb1 = Marubatsu()
for x, y in testcase:
    mb1.move(x, y)
print(mb1)
```

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

```diff_python
mb1 = Marubatsu()
- for data in testcase:
+ for x, y in testcase:
-   x, y = data
    mb1.move(x, y)
print(mb1)
```
</details>


実行結果
```
Turn x
ooo
xx.
...
```

#### テストを行う関数の定義

マークをまとめて配置する処理が記述できたので、下記のプログラムのように、先程記述した、テストケースのテストを行うプログラムを後ろに記述して __組み合わせます__。

実行結果から正しく動作することが確認できます。

```python
testcase = [
    [ 0, 0 ], # 着手順とゲーム盤
    [ 0, 1 ], # 135  ooo
    [ 1, 0 ], # 24.  xx.
    [ 1, 1 ], # ...  ...
    [ 2, 0 ],
]

mb1 = Marubatsu()
for x, y in testcase:
    mb1.move(x, y)
print(mb1)

if mb1.judge() == Marubatsu.CIRCLE:
    print("ok")
else:
    print("error!")
```
実行結果
```
Turn x
ooo
xx.
...

ok
```

__テストケースを表すデータ__ に対して __テストを行う__ 処理が記述できたので、この処理を行う `test_judge` という関数を下記のプログラムのように定義する事にします。この関数のブロックの中のプログラムは、関数にする前のプログラムと __基本的には同じ__ ですが、関数の中で `Marubatsu` クラスの __インスタンスは 1 つしか存在しない__ ので、`mb1` という名前を `mb` という名前に変更した点が異なります。また、最初に定義した `test_judge` の __仮引数 `testcase`__ に代入する値は、テストケースそのものでしたが、下記の `test_judge` の場合は、__テストケースを表すデータ__ である点に注意して下さい。

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

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

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

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

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

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

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

下記のプログラムのように、`test_judge` の実引数に `testcase` を記述して呼び出すことで、テストを正しく行えることが確認できます。

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

ok
```

# 次回の記事について

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

今回の記事で作成した `test_judge` はまだ __未完成__ なので、このままでは命令網羅(C0) の残りのテストケースのテストで使うことはできません。次回の記事ではこの関数を完成させ、命令網羅(C0) のテストを完了することにします。

# テストのプログラムのモジュールについて

テストで使用する関数は、〇× ゲームのプログラムとは __直接関係がない__ プログラムなので、`test.py` というファイルに保存することにします。なお、`test.py` の中に記述する `test_judge` は、`marubatsu` モジュールで定義された `Marubatsu` クラスを利用するので、先頭に `from marubatsu import Marubatsu` を記述する必要があります。

`test.py` の内容は、`marubatsu.py` と同様に、本記事で入力したプログラムの中のリンクから参照できるようにします。

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

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

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

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

以下のリンクは、今回の記事で作成した test.py です。

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

# 次回の記事

https://qiita.com/ysgeso/items/273ed49a00b90c753031