Pythonで〇×ゲームのAIを一から作成する　その31　〇の勝利の判定に対するテスト

# 目次と前回の記事

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

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

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

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

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

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

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

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

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

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

`test.py` が扱うテストケースは、Excel 座標を `","` で区切って連結したデータです。

https://github.com/ysgeso/marubatsu/blob/master/031/test.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) で、__勝敗判定__ を行う下記の `Marubatsu` クラスの __`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` メソッドのような、__複雑な処理__ を行う関数を __実装__ した場合は、__テストを行う__ ことが __重要__ です。

[以前の記事](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38)では、__テストの手法__ の一つである、__制御フローテスト__ について説明し、制御フローテストには、下記の表のような、いくつかの __種類がある__ 事を説明しました。

次に、[以前の記事](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) で、下記の表の中の __命令網羅__（C0）、__分岐網羅__（C1）のテストを行いましたが、これらのテストでは `test_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)  | なし  | 最大  | 最大  |

[以前の記事](https://qiita.com/ysgeso/items/273ed49a00b90c753031#%E6%9D%A1%E4%BB%B6%E7%B6%B2%E7%BE%85c2-%E3%81%AB%E3%82%88%E3%82%8B%E3%83%86%E3%82%B9%E3%83%88)で説明したように、本記事では条件網羅（C2）、判定条件/条件網羅（CDC)、複数条件網羅（MCC）は __行いません__。今回の記事では残りのテストのうち、__改良条件判断網羅__ （MC/DC）のテストを開始します。


# 改良条件判断網羅（MC/DC）によるテスト

[改良条件判断網羅](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 と表記します） では、以下のようなテストを行います。

条件分岐の __条件式の中__ に、and または or の __論理演算子__ が記述されている場合、それらで連結された __それぞれの式の値__ が、__全体の条件式__ の計算結果に __影響を及ぼす__（因果関係がある）かどうかを __考慮__ したテストを行う。

__MC/DC__ のテストを行うために必要な __テストケースを求める方法__ は、[以前の記事](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#mcdc-%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E3%82%B1%E3%83%BC%E3%82%B9%E3%81%AE%E6%B1%82%E3%82%81%E6%96%B9)で説明したように、__一般的__ にはそれほど __簡単ではありません__ が、__条件式__ が __or 演算子のみ__ で __連結__ されている場合は、[以前の記事](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#or-%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%AE%E3%81%BF%E3%81%A7%E9%80%A3%E7%B5%90%E3%81%95%E3%82%8C%E3%81%9F%E5%A0%B4%E5%90%88)で説明した下記の方法で __簡単に求める__ ことができます。

* __or 演算子__ で __連結__ された __式__ の計算結果が __すべて `False` になる__ テストケース
* __or 演算子__ で __連結__ された __式__ の計算結果のうち、__1 つだけ__ が __`True` になる__ テストケース

`judge` メソッドには __〇 の勝利__、__× の勝利__、__引き分け__ を __判定__ する __3 つの if 文__ が記述されており、__前者の 2 つ__ は __条件式__ が __or 演算子のみ__ で __連結__ されているので、__それら__ の if 文に対する __テストケース__ を __簡単に求める__ ことが出来ます。引き分けの場合に関しては次回の記事で説明します。

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

まず、__〇が勝利した場合__ の下記の if 文に対する __テストケースを求める__ ことにします。

```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:
        winner = Marubatsu.CIRCLE
```

上記の if 文の条件式は、__8 つの式__ が __or 演算子__ で __連結__ されており、__それぞれの式__ は、〇 が下図のように、直線上に __3 つ並んでいる__ かどうかを __判定__ しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/faac20d3-9f27-2b9f-968d-8e8748b48981.png" width="250">

## すべてが `False` になるテストケース

この条件式の中で、「__or 演算子__ で __連結__ された __式__ の計算結果が __すべて `False`__」になるテストケースとして __最も簡単__ なものは、ゲーム盤に一つも __マークが配置されていない__ 状態です。[以前の記事](https://qiita.com/ysgeso/items/728cee8982fe0ed9da38#%E3%83%86%E3%82%B9%E3%83%88%E3%82%B1%E3%83%BC%E3%82%B9)で説明したように、__テストケース__ は、「__テストデータ__ と、__期待される処理__ を __組み合わせたもの__」なので、「ゲーム盤に一つもマークが配置されていない状態」で `judge` メソッドを実行した際に __期待される処理__ である、__`judge` メソッド__ の __返り値__ が何になるかを __考える必要__ があります。

ゲーム盤に一つもマークが配置されていない場合は、__〇の勝利__、__×の勝利__、__引き分け__ の __いずれの場合でもない__ ので、`judge` メソッドの __3 つの if 文__ の __条件式__ の計算結果は __すべて `False`__ になります。従って、`judge` メソッドの最初でローカル変数 __`winner` に代入__ された __`None` という値__ は、__変更されることは無い__ ので、`judge` メソッドの __返り値__ は __`None` になる__ ことが __期待されます__。

下図の __赤い線__ と __枠線__ が、ゲーム盤に __マークが配置されていない場合__ に `judge` メソッドを実行した場合の __処理の流れ__ を表しており、図から、__関数の返り値__ が __`None` になる__ ことがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/57c99013-4868-6b8c-4704-c2a782c06805.png" width="500">

本記事では、__着手__ を行う __複数の座標__ を表す __データ構造__ として、[以前の記事](https://qiita.com/ysgeso/items/6ca248133ff4d59a81d4#%E8%A4%87%E6%95%B0%E3%81%AE-excel-%E5%BA%A7%E6%A8%99%E3%82%92%E8%A1%A8%E7%8F%BE%E3%81%99%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0-%E3%81%9D%E3%81%AE-2) で説明した __Excel 座標__ を __`","`__ で区切って __連結__ するデータ構造を採用します。下記は、そのデータ構造でゲーム盤に一つもマークが配置されていない場合の __テストケースを記述__ したプログラムです。このテストケースでは、__着手を行わない__ ので、着手を表すデータは __空の文字列__ である __`""`__ になります。

```python
from marubatsu import Marubatsu

testcases = [
    # ゲーム盤に一つもマークが配置されていない場合のテストケース
    [                     
        "",
        None, 
    ],           
]
```

### テストの確認

このテストケースに対して `test_judge` でテストを行うと、以下のような __エラーが発生__ します。

```python
from test import test_judge

test_judge(testcases)
```
実行結果
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\031\marubatsu.ipynb セル 2 line 3
      1 from test import test_judge
----> 3 test_judge(testcases)

File c:\Users\ys\ai\marubatsu\031\test.py:25, in test_judge(testcases)
     23 mb = Marubatsu()
     24 for coord in testdata.split(","):
---> 25     x, y = excel_to_xy(coord)            
     26     mb.move(x, y)
     27 print(mb)

File c:\Users\ys\ai\marubatsu\031\test.py:48, in excel_to_xy(coord)
     34 def excel_to_xy(coord: str) -> tuple(int):
     35     """ Excel 座標を xy 座標に変換する.
     36     
     37     "A1" のような、文字列で表現される Excel 座標を、
   (...)
     46         x 座標と y 座標を要素として持つ tuple
     47     """
---> 48     x, y = coord
     49     return "ABC".index(x), int(y) - 1

ValueError: not enough values to unpack (expected 2, got 0)
```

:::note
上記のエラーメッセージは、以下のような意味を持ちます。

* __ValueError__
値（value）に関するエラー
* __not enough values to unpack (expected 2, got 0)__
展開する（to unpack）ために必要な値（value）が 2 つ期待されている（expected）が、0 個（got 0）しかないため足りない（not enough）
:::

### エラーの原因の究明

__エラーメッセージ__ から、__`excel_to_xy` のブロック__ の中で __`x, y = coord`__ の処理を行う際に __エラーが発生__ していることがわかります。また、この文は、__`coord` に代入__ された、__2 文字の Excel 座標__ の __1 文字目__ を __`x`__ に、__2 文字目__ を __`y`__ に __代入__ する処理ですが、__エラーメッセージ__ から、__`coord`__ に __代入__ されている __文字列の長さ__ が __0 である__ ことが __推測__ されます。

__エラーメッセージ__ から、__`excel_to_xy`__ は、`test_judge` のブロックの、__下記__ のプログラムの __部分__ から __呼び出されている__ ので、この部分について __調べてみる必要__ がありそうです。

```python
        for coord in testdata.split(","):
            x, y = excel_to_xy(coord) 
```

上記のプログラムでは、for 文の __反復可能オブジェクト__ として、__`testdata.split(",")` が記述__ されていますが、__このテストケース__ の場合は、__`testdata`__ には、__空文字__ である __`""` が代入__ されているので、__その状態__ で、下記のプログラムのように、__`testdata.split(",")` を実行__ してみます。

```python
testdata = ""
print(testdata.split(","))
```
実行結果
```
['']
```

実行結果からわかるように、__`testdata.split(",")`__ によって、__空文字__ を __要素__ として持つ __list が得られる__ ことがわかります。そのため、__`for coord in testdata.split(","):`__ を実行すると、__`coord` に空文字が代入__ された状態で、__for 文のブロックが実行__ され、その __結果__ として、`excel_to_xy` のブロックの中で __`x, y = ""` が実行される__ ため、先ほどの __エラーが発生__ します。下記のプログラムによって、__実際__ に `x, y = ""` を実行し、__同じエラーが発生__ することを __確認__ できます。

```python
x, y = ""
```
実行結果
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\031\marubatsu.ipynb セル 4 line 1
----> 1 x, y = ""

ValueError: not enough values to unpack (expected 2, got 0)
```

### `split` メソッドに関する注意点

先程のテストケースでは、一つも __着手を行わない__ ことを表す __データ__ を __空文字で表現__ しました。これは、__着手するデータ__ を Excel 座標という __文字列__ で __`","` で区切って表現__ することと、__着手__ するデータが __存在しない__ ということから、__人間にとって__ は、__自然な表現方法__ だと思います。

また、__`split` メソッド__ は、__特定の文字列__ で __区切られた__ 文字列を、__list に変換__ する処理を行うので、Excel 座標を __`","` で区切って連結__ したデータを __list に変換__ する際に、__`split` メソッドを利用__ するのも __自然な考え方__ だと思いますが、そこに今回のエラーの __落とし穴__ があります。

この __エラーの原因__ は、__空文字__ を __`split` メソッド__ で __list に変換__ すると、__空の list に変換__ されると __錯覚__ してしまった点にあります。実際に、__筆者もそのような錯覚__ をしていたため、今回の記事を記述するまで、このような __バグ__ が `test_judge` に __存在__ することに __気が付いていませんでした__。

人間は __先入観を持つ__ 生き物です。関数の説明の __概要から__、勝手にその関数が行う処理を __理解したつもりになる__ ことは良くある事なので、その点に __注意する必要__ があります。

:::note
上記のように、__他人が作成した関数__ や __プログラム__ が __実際に行う処理__ と、その関数やプログラムを __利用する人__ が __期待する処理__ が __微妙に異なる__ ことによって __バグが発生__ することは、__よくある事__ です。そのようなことを __避ける__ ためには、関数やプログラムの __使い方の説明__ を __よく読んで正確に理解__ する必要があります。

__可能であれば__ そうすべきですが、時間などの都合によって、自分が利用する __すべての関数__ の説明をしっかりと読んで、__細部まで理解する__ ことが __現実的ではない__ 場合があります。例えば、これまでに紹介した `print` などの組み込み関数の説明を細部まで読んで __すべて理解__ している人は __あまり多くない__ と思います。筆者も他人が作成した関数を利用する際に、それらのすべての関数の説明を熟読し、詳細まで理解して使っているわけではありません。

また、説明を __熟読したとしても__、__説明__ そのものが __間違っていたり__、内容が __古かったりする__[^1]こともあります。従って、どれだけ注意しても、このようなバグが発生することを __完全に無くす__ ことは __現実的には無理__ だと思いますので、__バグが発生した後__ で、__バグの原因__ を __見つけ出して修正する__ という __技術が重要__ になります。

何度も同じようなことを書いているので、本当にくどいと思っている方がいるかもしれませんが、そのような技術を身に付けるためには、そのような __バグを体験__ し、__実際に修正__ するという __経験を積む__ 以外の方法はないと思いますので、本記事ではそのようなバグが発生した場合に、なるべく裏でこっそりと修正した記事を載せるのではなく、正直にそのようなバグが発生したことを記述し、その修正方法まで載せることにしています。
:::

このバグを修正する方法を説明する前に、空文字に対して `split` メソッドを実行した際に、空の list に変換されない __理由__ について説明します。

例えば、__`"ABC".split(",")`__ のように、__文字列の中__ に1 つも __`","` が記述されていない__ 場合に、`split` メソッドの __実引数に `","` を記述__ して __list に変換__ する処理を考えてみて下さい。この場合に、文字列の中に __`","` が記述されていないから__ といって、__空の list に変換される__ と考える人は __ほとんどいない__ のではないでしょうか？実際に、下記のプログラムのように、この処理によって、__`['ABC']`__ という、__元の文字列__ を __要素__ とする __list に変換__ されます。

```python
print("ABC".split(","))
```
実行結果
```
['ABC']
```

__空文字__ のことを、__特別な文字列__ であると __考えている__ 人がいる __かもしれません__ が、`"ABC"` も、空文字も、__文字列である__ ことに __変わりはありません__。従って、__`"".split(",")`__ を実行すると、`"ABC".split(",")` と __同様__ に、__元の文字列__ を __要素__ とする `['']` という list に変換されます。

:::note warn
文字列型のデータの __`split` メソッド__ は、__以下の処理__ を行う。

* __文字列__ を、`split` メソッドの __実引数__ に記述した文字列で __区切り__、__区切られた文字列__ を __要素__ とする __list に変換__ する
* __文字列の中__ に、`split` メソッドの __実引数__ に記述した __文字列が存在しない__ 場合は、__元の文字列のみ__ を __要素__ とする __list に変換__ する
* `split` メソッドによって、__空の list__ が __作成__ される __ことはない__
:::

### エラーの修正

テストケースに記述した __空文字__ を __空の list__  に __変換したい__ 場合は、下記のプログラムのように、__if 文__ を使って、__文字列__ が __空文字であるか__ どうかを __チェックする__ 必要があります。下記のプログラムでは、__list に変換__ したデータを __`coords`__ というローカル変数に __代入する__ ことにし、1 ~ 4 行目の if 文で、__空文字の場合__ は __空の list__ を、__そうでない場合__ はこれまで通り、__`split` メソッド__ を使って __list に変換__ したデータを __`coords` に代入__ しています。また、5 行目で __`coords`__ を __for 文__ の __反復可能オブジェクト__ として __記述__ するように修正しています。

```python
1       if testdata == "":
2           coords = []
3       else:
4           coords = testdata.split(",")
5       for coord in coords:
6           x, y = excel_to_xy(coord) 
```

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

```python
        if testdata == "":
            coords = []
        else:
            coords = testdata.split(",")
        for coord in coords:
            x, y = excel_to_xy(coord) 
```
</details>

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

```diff_python
+       if testdata == "":
+           coords = []
+       else:
+           coords = testdata.split(",")
-       for coord in testdata.split(","):
+       for coord in coords:
            x, y = excel_to_xy(coord) 

```
</details>

上記をもっと __簡潔に記述__ したい人は、[以前の記事](https://qiita.com/ysgeso/items/ed1eb1cacf9a3e7dfa29#%E4%B8%89%E9%A0%85%E6%BC%94%E7%AE%97%E5%AD%90) で説明した、__三項演算子__ を使って、下記のプログラムのように記述すると良いでしょう。本記事ではこのように記述することにします。

```python
        coords = [] if testdata == "" else testdata.split(",")
        for coord in coords:
            x, y = excel_to_xy(coord) 
```

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

```diff_python
-       if testdata == "":
-           coords = []
-       else:
-           coords = testdata.split(",")
+       coords = [] if testdata == "" else testdata.split(",")
        for coord in coords:
            x, y = excel_to_xy(coord) 
```
</details>

:::note
`coords` を使わずに、下記のように __さらに短く記述__ することも可能ですが、プログラムが __わかりにくくなる__ という欠点があるので、本記事では __採用しません__。

```python
        for coord in [] if testdata == "" else testdata.split(","):
            x, y = excel_to_xy(coord) 
```

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

```diff_python
-       coords = [] if testdata == "" else testdata.split(",")
-       for coord in coords:
+       for coord in [] if testdata == "" else testdata.split(","):
            x, y = excel_to_xy(coord) 
```
</details>
:::

下記のプログラムは、上記の修正を行った `test_judge` です。なお、この関数の中で `excel_to_xy` を使用しているので、1 行目でこの関数をインポートしています。

```python
from test import excel_to_xy

def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        coords = [] if testdata == "" else testdata.split(",")
        for coord in coords:
            x, y = excel_to_xy(coord)            
            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(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
+       coords = [] if testdata == "" else testdata.split(",")
-       for coord in testdata.split(","):
+       for coord in coords:
            x, y = excel_to_xy(coord)            
            mb.move(x, y)
        print(mb)

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


修正後の `test_judge` を使うことで、下記のプログラムのようにエラーが発生しなくなります。また、実行結果から、正しくテストを行うことができることが確認できます。

```python
test_judge(testcases)
```
実行結果
```
Turn o
...
...
...

ok
```

### `None` の判定と、is、is not 演算子

実は、この時点で気づいたのですが、`test_judge` にはあまりよくない記述があります。それは、`test_judge` の下記の部分で、__`==` 演算子__ を使って、__`mb.judge()` の返り値__ と、__期待される返り値__ の __比較__ を行っている点です。

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

`mb.judge()` が、__数値型__ や、__文字列型__ のデータ __のみを返す__ 場合は、__`==` 演算子__ を使った比較で __全く問題はない__ のですが、`mb.judge()` は、ゲームの __決着がついていない__ 場合は、__`None` を返す__ ように定義しています。__Python__ では、__`None` を `==` 演算子で比較__ することは __推奨されていません__。

#### `==` 演算子と同値性

その理由は、__`==`__ 演算子が、__同じ意味__ を持つ値であるかどうかを __判定する__ という処理が行われるからです。そのような性質を __同値性__ と呼びます。

意味が分かりづらいと思いますので、具体例を挙げて説明します。これまで __数字__ を表すデータの事を、__区別する必要がない__ 場合は __数値型__ のように、__区別せず__ に呼んできましたが、__Python__ では、__`0`__ のような __整数__ のデータは __整数型__（int）、__`1.2`__ のような __小数点を含む__ データは __浮動小数点数型__（float）という、__異なるデータ型__ で表現されます。そのため、Python では、__整数型__ と __浮動小数点数型__ の、__2 種類__ のデータ型で __`0`__ という数値を __表現__ することができます。

Python では、数値に __小数点をつけて記述__ することで、__浮動小数点数型__ のデータを表現するという __決まり__ になっているので、__`0` は整数型__、__`0.0` は浮動小数点数型__ のデータです。そのことは、__データ型__ を返す、組み込み関数 __`type`__[^2] を使って下記のプログラムのように確認できます。

```python
print(type(0))
print(type(0.0))
```
実行結果
```
<class 'int'>
<class 'float'>
```

Python では、__`0` と `0.0`__ は、__異なるデータ型__ のデータですが、その __意味__ は __同じゼロ__ です。先程説明したように、__`==`__ は __同じ意味__ のデータであるかどうかを表す __同値性を判定__ する演算子なので、下記のプログラムのように __`0` と `0.0`__ を __`==` で比較__ すると __`True`__ になります。

```python
print(0 == 0.0)
```
実行結果
```
True
```

__異なるデータ型__ のデータを __`==` 演算子__ で __比較__ した場合に __行われる処理__ はデータ型によって異なります。そのため、「__`None`__」と、「`None` ではないが __`None` と同じ意味__ を持つデータ」を __`==` 演算子__ で __比較__ すると __`True`__ になる __場合__ があります。これが、__`None` であるかどうか__ を __`==` で比較__ することが __推奨されない理由__です。

なお、__等しくない__ ことを __判定__ する __`!=` 演算子__ についても __同様の理由__ で、__`None` でない__ ことを __判定__ するために使うことは __推奨されません__。

#### is 演算子と同一性

データが __完全に同じ__ ものであることを、__同一性__ と呼び、Python では、__is 演算子__ によって __前後のデータ__ が __同一__ であるかどうかを __判定__ することができます。

下記のプログラムのように、__`0` と `0.0`__ は、同じ意味を持ちますが、__異なるデータ__ なので、__is 演算子__ を使って __比較__ すると、計算結果が __`False`__ になります。

```python
a = 0
b = 0.0
print(a == b)
print(a is b)
```
実行結果
```
True
False
```

__同様__ の演算子に、__同一でない__ ことを判定する演算子として __`is not`__ という __演算子__ があります。`is not` で __一つの演算子__ である点に注意して下さい。

#### is 演算子の注意点

Python の __is 演算子__ は、データを管理する __オブジェクトが等しい__ かどうかによって __同一性__ を __判定__ します。従って、[以前の記事](https://qiita.com/ysgeso/items/911fafe3f80a805165d1#%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%A8%E3%83%AA%E3%83%86%E3%83%A9%E3%83%AB) で説明したように、__全く同じ数値__ データであっても、__オブジェクトの `id`__ が __異なる__ ような場合に、__is 演算子で比較__ を行うと、下記のプログラムのように、そのデータを管理する __オブジェクトが異なる__ という理由で __`False` になる場合__ があります。従って、__is 演算子__ を __数値型__ や、__文字列型__ どうしの __比較__ を行う際に __使うべきではありません__。

```python
a = 1.23
b = 1.23
print(a is b)
```
実行結果（OS や Python のバージョンによっては `True` になる場合があります）
```
False
```

:::note warn
初心者のうちは、__is 演算子__ は、__`None` であるかどうかを判定__ する場合 __のみで使う__ と覚えておき、必要になってから他の is 演算子の用途について学べば十分だと思います。
:::

### プログラムの修正

先程のプログラムの修正方法の一つは、下記のプログラムのように、テストケースの __期待される値__ が __`None` の場合__ は、__`is` 演算子__ を、__それ以外の場合__ は __`==` 演算子__ を使って比較を行います。

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

上記のプログラムは、__無駄が多い__ ので、__and と or 演算子__ を使って下記のように記述することができます。ただし、何れの方法を使っても、__判定の処理が複雑になる__ ことには変わりはありません。

```python
        if (winner is None and mb.judge() is winner) or \
           (winner is not None and mb.judge() == winner):
            print("ok")
        else:
            print("error!")
```

__別の方法__ として、__決着がついていない場合__ に、__`judge`__ メソッドの __返り値__ として、__`None` 以外__ のデータを __返す__ ように修正するという方法があります。この方法であれば、__`test_judge` を変更__ する __必要はなくなる__ ので、本記事では、その方法を採用することにします。

この方法を採用する場合は、__決着がついていない場合__ に、`judge` メソッドの __返り値__ に __何を返すか__ を __決める__ 必要があります。決着がついていない場合を __除くと__、`judge` メソッドが __返すデータ__ は、下記のプログラムのように、`Marubatsu` クラスの先頭に、__クラス属性として定義__ していました。

```python
class Marubatsu:
    EMPTY = "."
    CIRCLE = "o"
    CROSS = "x"
    DRAW = "draw"
```

そこで、__決着がついていない場合__ を表す値も __同様__ に、__クラス属性__ として __定義__ する事にします。「決着がついていない」ということは、「__ゲームをプレイ中__」であると __言い換える__ ことが出来るので、クラス属性の名前を __`PLAYING`__ という名前にすることにします。また、__その値__ は、他のクラス属性と __区別__ できれば __なんでも構わない__ ので、`DRAW` と同様に、__`"playing"`__ という __文字列で表現__ することにします。そこで、下記のプログラムのように、__`Marubatsu` クラス__ の __クラス属性__ に、__`PLAYING` を追加__ します。

```python
Marubatsu.PLAYING = "playing"
```

次に、__テストケース__ を下記のように __修正__ します。

```python
testcases = [
    # ゲーム盤に一つもマークが配置されていない場合のテストケース
    [                     
        "",
        Marubatsu.PLAYING, 
    ],           
]
```

修正した `test_judge` を使ってテストを実行すると、筆者もうまくいくと思っていたのですが、実行結果に __error! が表示__ されてしまいます。その理由について少し考えてみて下さい。

```python
test_judge(testcases)
```
実行結果
```
Turn o
...
...
...

error!
```

__`error!` が表示__ されるのは、__`test_judge`__ のブロックの __下記の部分__ です。従って、__`mb.judge()` の返り値__ か、__`winner` の値__ の __いずれか__ 、または __その両方__ が間違っている __可能性が高い__ でしょう。

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

__これまでのエラー__ の多くは、__プログラムが停止__ するようなエラーで、その際に表示される __エラーメッセージ__ は、__Python が作成__ した __詳細__ なもので、__それを頼り__ にエラーを __修正__ してきました。

一方、__今回のエラー__ は、__プログラムが停止__ するようなエラー __ではなく__、表示される __エラーメッセージ__ は、__自分で何を表示するか__ を __`print` で記述__ することで表示されたものです。このエラーメッセージは、__エラーがあることしか説明__ していないので、このメッセージから `mb.judge()` と `winner` の __どちらが間違っているか__ を __推測__ することは __できません__。この __問題__ は、エラーの __メッセージ__ に、エラーの __原因を特定__ できるような __情報が含まれていない__ ことが原因です。従って、下記のプログラムのように、__エラーの原因__ となる、__`mb.judge()`__ と __`winner` の値__ を __エラーメッセージに含める__ ように修正することで、メッセージから __エラーの原因__ が __推測しやすく__ なります。

なお、下記のプログラムで表示される __エラーメッセージ__ は __一例__ です。これより詳しいエラーメッセージを表示したい場合は、`print` で表示する内容を __自由に修正__ して下さい。

```python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in [] if testdata == "" else testdata.split(","):
            x, y = excel_to_xy(coord)            
            mb.move(x, y)
        print(mb)

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

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

```diff_python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in [] if testdata == "" else testdata.split(","):
            x, y = excel_to_xy(coord)            
            mb.move(x, y)
        print(mb)

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


修正後に `test_judge` を実行すると、以下のような実行結果が表示されます。

```python
test_judge(testcases)
```
実行結果
```
Turn o
...
...
...

test_judge error!
mb.judge(): None
winner:     playing
```

実行結果から、__期待される値__ である __`winner`__  には __正しい__ `"playing"` が __代入__ されていますが、__`mb.judge()` の返り値__ が __`None` のまま__ で __間違っている__ ことがわかります。__原因__ が、__`judge` メソッドにある__ ことがわかったので、__関数の定義__ を __調べてみる__、下記のプログラムのように、最初に __`winner`__ に __`None` を代入したまま__ であることがわかります。

```python
    def judge(self):
        # 判定を行う前に、決着がついていないことにしておく
        winner = None

    略
```

従って、このバグは、下記のプログラムのように、`judge` メソッドの最初で、__`winner`__ に __`Marubatsu.PLAYING` を代入__ するようにすることで修正することが出来ます。なお、長くなるので下記には `judge` メソッドの先頭の一部のみ記述しますが、github にアップロードする marubatsu.ipynb のほうでは、全体を記述します。

```python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
    winner = Marubatsu.PLAYING

    略

Marubatsu.judge = judge   
```

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

```diff_python
def judge(self):
    # 判定を行う前に、決着がついていないことにしておく
-   winner = None
+   winner = Marubatsu.PLAYING
```
</details>

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

:::note
決着がついていないことを表すデータを、`None` から、`Marubatsu.PLAYING` に修正したことで、`judge` メソッドのローカル変数 __`winner` の名前__ と、その変数に __代入するデータ__ の __整合性がとれなくなります__。具体的には、__勝者を表す `winner`__ という名前の変数に、`Marubatsu.PLAYING` という、ゲームの __勝者__ と __無関係__ なデータが代入される __可能性が生じます__。このような場合は、`winner` という変数の名前を別のより __ふさわしい名前に変更__ することも考えられますが、この問題は __些細な問題__ であると考えて、変数の名前を __修正しない__ という方法も考えられます。本記事では、このままでもプログラムの意味が著しくわかりにくくなることは無いと思いましたので、修正しないことにします。
:::

## 1 つだけが `True` になるテストケース

次に、__or 演算子__ で __連結__ された __式__ の中の __1 つだけ__ が __`True`__ になるテストケースを考えます。

〇の勝利を判定する if 文の条件式の中の、or 演算子で連結された __それぞれ__ の __式が `True`__ になるのは、__下図__ のマスに __〇 が配置された場合__ です。下図の __8 通り__ の __いずれの場合__ でも、__同時__ に __2 つ以上__ の場所で 〇 が __3 つ並ぶことはない__ ので、__1 つだけ__ が __`True` になる__ ことが __保証__ されます。従って、__下図__ のように __〇 が配置__ された、__8 つ__ のテストケースを __用意すれば良い__ ことがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/faac20d3-9f27-2b9f-968d-8e8748b48981.png" width="250">

次に、その __8 つのテストケース__ に対する __`judge` メソッド__ の __期待される返り値__ について考える必要があります。__〇 を 3 つ配置__ するということは、__その間__ に __× は 2 回__ しか __配置__ しないことになるので、用意するテストケースで __どこに × を配置__ しても、__× が勝利__ することは __ありません__ 。従って、__× の勝利を判定__ する if 文の __条件式__ の計算結果は __必ず `False`__ になります。

テストケースでは、__着手__ は __〇 の 3 回__、__× の 2 回__ の、__計 5 回__ になるので、__引き分け__ になることは __ありません__。従って、__引き分けを判定__ する if 文の __条件式__ の計算結果も __必ず `False`__ になります。

上記の事から、__× は__ 空いているマスであれば、__どのマスに配置しても良い__ こともわかります。

下図は、上記の __テストケース__ の一つに対する __`judge` メソッド__ の __処理の流れ__ を表す __フローチャート__ で、__赤い枠線と線__ が行われる __処理の流れ__ を表します。図から、__`winner`__ に関する __代入処理__ は、__赤字__ の __`winner = Marubatsu.CIRCLE`__ が __最後に行われる__ ので、`judge` メソッドの __期待される返り値__ は __`Marubatsu.CIRCLE`__ であることがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c49263b5-6e5b-fa6e-d17a-274a4b30cf39.png" width="500">

下記のプログラムは、先程のテストケースに、__1 つだけ__ が __`True` になる__ テストケースを __3 つ加えた__ ものです。8 つすべてを加えない理由についてはこの後で説明します。

なお、__これまで__ はそれぞれのテストケースの __盤面__ を __コメントで記述__ していましたが、テストケースを __数多く入力__ 際に、そのような __コメント__ を毎回 __記述するのは大変__ なので以降は記述しません。

```python
testcases = [
    # ゲーム盤に一つもマークが配置されていない場合のテストケース
    [                     
        "",
        Marubatsu.PLAYING, 
    ],           
    # 〇の勝利のテストケース
    [
        "A1,A2,B1,B2,C1",
        Marubatsu.CIRCLE
    ],
    [
        "A2,A1,B2,B1,C2",
        Marubatsu.CIRCLE
    ],
    [
        "A3,A1,B3,B1,C3",
        Marubatsu.CIRCLE
    ],
]
```

このテストケースで `test_judge` でテストを行うと以下のような実行結果が表示されます。

```python
test_judge(testcases)
```
実行結果
```
Turn o
...
...
...

ok
Turn x
ooo
xx.
...

ok
Turn x
xx.
ooo
...

ok
Turn x
xx.
...
ooo

ok
```

上記のテストデータを実際に入力する際に、__入力した座標__ が __正しいか__ どうかが __自信が持てない__ 人が多いのではないかと思います。__筆者も__ 入力する際に間違った入力をしていることに気づいて __何度か修正__ しました。また、__入力したデータ__ が __正しいか__ どうかの __確認__ を、入力したデータ __そのもの__ を __目で見て確認__ することは、Excel 座標にかなり慣れないと __困難です__。

__`test_judge`__ が行う __処理の中__ で、テストケースのデータを元に着手が行われた __ゲーム盤を画面に表示__ するようにしたのは、__入力__ した __テストケース__ が __正しいか__ どうかを、わかりやすく __確認できるようにする__ ためです。テストケースのデータを入力し、`test_judge` を実行した際は、必ず実行結果を見て確認するようにして下さい。

上記の実行結果で表示される __ゲーム盤の表示内容__ から、__意図通り__ のテストケースが __記述されている__ ことが __確認__ できます。また、__すべて__ のテストケースで __`ok` が表示__ されているので、__入力したテストケース__ に対して __`judge` メソッド__ が __期待された処理__ を行うことが __確認__ できます。

:::note warn
__`test_judge`__ は、__`judge` メソッド__ が __期待される処理を行う__ かどうかを __確認__ する __だけでなく__、__入力したテストケース__ が __正しい__ かどうかを __確認__ する __役割__ を持つ。
:::

### 期待される返り値ごとに、テストケースをまとめる方法

先程のテストケースに、__8 つ__ のテストケースを __すべて追加しなかった理由__ について説明します。

下記の先程のテストデータをよく見て下さい。__無駄な記述__ があると思う人はいないでしょうか？

```python
testcases = [
    # ゲーム盤に一つもマークが配置されていない場合のテストケース
    [                     
        "",
        Marubatsu.PLAYING, 
    ],           
    # 〇の勝利のテストケース
    [
        "A1,A2,B1,B2,C1",
        Marubatsu.CIRCLE
    ],
    [
        "A2,A1,B2,B1,C2",
        Marubatsu.CIRCLE
    ],
    [
        "A3,A1,B3,B1,C3",
        Marubatsu.CIRCLE
    ],
]
```

上記のテストケースのデータ構造には、それほど大きな欠点とは言えないかもしれませんが、いくつかの問題があるため、__改良の余地__ があります。

問題点の一つは、__3 つ__ の 〇 が勝利するテストケースの __それぞれ__ に、__`Marubatsu.CIRCLE` が記述__ されている点です。これは、今後 __〇 が勝利__ する場合のテストケースを __記述するたび__ に、__`Marubatsu.CIRCLE` を記述__ する __必要がある__ ということを意味しますが、__冗長__ だと思いませんか？

もう一つの問題点は、__異なる__ `judge` メソッドの __期待される返り値__ を持つ __テストケース__ が、__1 つの list__ で __まとめられている__ という点です。__現時点__ では、__〇 の勝利__ のテストケースを __表す__ list の __要素__ が __隣り合って__ まとまっていますが、__今後__ この list に、__他の期待される返り値__ を持つテストケースを __追加__ した際に、__同じ期待される返り値__ を持つテストケースが、list の中で __バラバラに配置__ されてしまう __可能性が高く__ なります。もちろん、バラバラにならないように気をつけながらデータを記述することもできますが、いちいちそのようなことを __注意しながら__ データを __記述__ するのは __面倒__ ですし、__間違って__ バラバラになるように __記述__ してしまった __場合__ に、それをバラバラにならないように __記述し直す__ のは __大変__ です。

`judge` メソッドをテストするテストケースには、__4 種類__ の __期待される返り値__ が存在します。また、その 4 種類の __期待される返り値__ に __対して__ 、__それぞれ複数__ のテストケースを __記述__ することができます。このことを、別の言葉で表現すると、__1 つ__ の __期待される返り値__ から、__複数のテストデータ__ への __対応づけ__ が行われることを意味します。また、__期待される返り値__ は __文字列__ で表現されるので、この __対応づけ__ は、__dict のキー__ に __期待される返り値__ を、その __キーの値__ に __対応__ する __複数__ の __テストデータ__ を __要素__ とする __list__ を __代入__ するという __データ構造__ で表現することができます。

具体的には、下記のプログラムのようにテストケースを記述することができます。なお、データが list から __dict に変わった__ ので、データを __囲う記号__ を `[]` から __`{}` に修正__ する必要があります。

```python
testcases = {
    # ゲーム盤に一つもマークが配置されていない場合のテストケース
    Marubatsu.PLAYING: [
        "",
    ],   
    # 〇の勝利のテストケース
    Marubatsu.CIRCLE: [
        "A1,A2,B1,B2,C1",
        "A2,A1,B2,B1,C2",
        "A3,A1,B3,B1,C3",
    ],
}
```

このデータ構造は、先程のデータ構造と異なり、__期待される返り値__ を表すデータを __dict のキー__ として  __1 箇所だけに記述__ するので、その分だけ __記述が簡潔__ になります。

また、__同じ__ 期待される返り値に __対応するテストデータ__ が、__同じ list__ の中に __集まっている__ ので、それぞれの __テストデータの意味__ が __わかりやすくなります__。

さらに、__テストケース__ の全体の __記述の行数__ も、__大幅に短く__ することができます。

このように、新しいデータ構造には様々な利点があるので、本記事では以降はこのデータ構造でテストケースを記述することにします。

:::note warn
本記事では、以降はテストケースのデータ構造を以下のように記述する。

* __dict__ で表現する
* dict の __キー__ は、__`judge` メソッド__ の __期待される返り値__ を表す
* __キーの値__ には、キーの値に __対応する__ テストデータを __要素とする list__ を __代入__ する
:::

:::note
テストケースを、__list を使わず__ に、下記のように記述すれば良いと思った人はいないでしょうか？残念ながら、下記のプログラムのように、__dict__ のデータを __記述__ する際に、__同じキー__ を __何度も記述__ すると、実行結果からわかるように、__最後に記述__ された __キーの値のみ__ が __代入__ されることになります。なお、下記のプログラムでは、先程 `testcases` に代入したデータを壊さないようにするために、`testcases2` という変数に値を代入しています。

```python
testcases2 = {
    # ゲーム盤に一つもマークが配置されていない場合のテストケース
    Marubatsu.PLAYING: "",
    # 〇の勝利のテストケース
    Marubatsu.CIRCLE:  "A1,A2,B1,B2,C1",
    Marubatsu.CIRCLE:  "A2,A1,B2,B1,C2",
    Marubatsu.CIRCLE:  "A3,A1,B3,B1,C3",
}

print(testcases2)
```
実行結果
```
{'playing': '', 'o': 'A3,A1,B3,B1,C3'}
```

上記のような処理が行われる __理由__ は、上記のプログラムを実行すると、下記のプログラムのような処理が行われるからです。dict のデータを記述して実行すると、下記のプログラムのように、__空の dict__ が __最初に作成__ され、その後で、__記述された順番通り__ に、__dict のキー__ に __値が代入__ されます。従って、上記のプログラムは、下記のように、__同じキー__ に対する __代入処理__ が __何度も行われ__、そのたびに __キーの値__ が __上書き__ されることになります。

```python
testcases2 = {}
testcases2[Marubatsu.PLAYING] = ""
testcases2[Marubatsu.CIRCLE] = "A1,A2,B1,B2,C1"
testcases2[Marubatsu.CIRCLE] = "A2,A1,B2,B1,C2"
testcases2[Marubatsu.CIRCLE] = "A3,A1,B3,B1,C3"

print(testcases2)
```
実行結果
```
{'playing': '', 'o': 'A3,A1,B3,B1,C3'}
```
:::

### `test_judge` の修正

テストケースの __データ構造が変わった__ ので、それに合わせて __`test_judge` を修正__ する必要があります。`test_judge` に対する修正は以下の通りです。

* 2 行目を `for winner, testdata_list in testcases.items():` に修正する
* 元のプログラムにあった __`testdata, winner = testcase` を削除__ する
* 3 行目を、`for testdata in test_data_list:` に修正し、__残り__ のプログラムの __インデントを右にずらす__ ことで、この __for 文のブロック__ の __プログラムになる__ ように修正する

2 行目では、[以前の記事](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) で説明した、for 文で dict の __キーと値の両方を取り出す繰り返し__ を記述しています。__`testcases` のキー__ は、`judge` メソッドの __期待される返り値__ を表すデータです。従って、このデータを __`winner`__ に __代入__ することで、__修正前と同じ処理__ を行うことができます。また、この修正により、元の `testdata, winner = testcase` は __必要が無くなった__ ので __削除__ しています。

`testcases` の __キーの値__ は、__キーに対応__ する __テストデータの list__ なので、その値を __`testdata_list`__ という名前の __変数に代入__ しています。そして、3 行目の for 文で、`testdata_list` の __先頭の要素__ から __順番に取り出し__ て __`testdata` に代入__ するという __繰り返し処理__ を記述することで、2 行目で `testcases` から取り出した、__`winner` に対応__ するテストデータの __テスト__ を __まとめて行う__ ことができます。なお、3 行目以降のプログラムは、__修正する前__ の `judge` メソッドのプログラムと、インデントが変わった点を除けば、__同一の内容__ になります。

```python
 1  def test_judge(testcases):
 2      for winner, testdata_list in testcases.items():
 3          for testdata in testdata_list:
 4              mb = Marubatsu()
 5              for coord in [] if testdata == "" else testdata.split(","):
 6                  x, y = excel_to_xy(coord)            
 7                  mb.move(x, y)
 8              print(mb)
 9
10              if mb.judge() == winner:
11                  print("ok")
12              else:
13                  print("test_judge error!")
14                  print("mb.judge():", mb.judge())
15                  print("winner:    ", winner)
```

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

```python
def test_judge(testcases):
    for winner, testdata_list in testcases.items():
        for testdata in testdata_list:
            mb = Marubatsu()
            for coord in [] if testdata == "" else testdata.split(","):
                x, y = excel_to_xy(coord)            
                mb.move(x, y)
            print(mb)

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

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

__4 行目以降__ の for 文のブロックには __インデントを追加__ していますが、それらの行の修正を示すと、かえってわかりづらく 3 行目以降の修正は示しません。

```diff_python
def test_judge(testcases):
-   for testcase in testcases:
+   for winner, testdata_list in testcases.items():
-       testdata, winner = testcase
+       for testdata in testdata_list:
            mb = Marubatsu()
            for coord in [] if testdata == "" else testdata.split(","):
                x, y = excel_to_xy(coord)            
                mb.move(x, y)
            print(mb)

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

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

### 残りのテストケースの記述

下記は、__新しいデータ構造__ で、__〇 が勝利__ した場合の __テストケースを記述__ したプログラムです。


```python
testcases = {
    # ゲーム盤に一つもマークが配置されていない場合のテストケース
    Marubatsu.PLAYING: [
        "",
    ],   
    # 〇の勝利のテストケース
    Marubatsu.CIRCLE: [
        "A1,A2,B1,B2,C1",
        "A2,A1,B2,B1,C2",
        "A3,A1,B3,B1,C3",
        "A1,B1,A2,B2,A3",
        "B1,A1,B2,A2,B3",
        "C1,A1,C2,A2,C3",
        "A1,A2,B2,A3,C3",
        "A3,A1,B2,A2,C1",
    ],
}
```

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。__実行結果を見て__、__正しいマス__ にマークが __配置__ されていることを __必ず確認__ して下さい。ちなみに筆者は、確認した結果、一度間違っていることが判明したので修正しました。

```python
test_judge(testcases)
```
実行結果
```
Turn o
...
...
...

ok
Turn x
ooo
xx.
...

ok
Turn x
xx.
ooo
...

ok
Turn x
xx.
...
ooo

ok
Turn x
ox.
ox.
o..

ok
Turn x
xo.
xo.
.o.

ok
Turn x
x.o
x.o
..o

ok
Turn x
o..
xo.
x.o

ok
Turn x
x.o
xo.
o..

ok
```

# 今回の記事のまとめ

今回の記事では、__MC/DC テスト__ の中で、__〇 の勝利を判定__ する __if 文__ に対する __テストケースを記述__ し、テストを行いました。その際に、__テストケース__ を表す __データ構造__ の __改良__ を行いました。

もちろん、MC/DC テストに限らず、__完璧なテスト__ を行うことは __不可能__ なので、__バグが絶対に存在しない__ ことまでは __保証できません__ が、`judge` メソッドの中で、__〇の勝利を判定__ する __if 文__ に関しては、バグが存在する __可能性が低い__ ということは言えます。

本当は今回の記事でテストを終わらせる予定だったのですが、筆者も気づいていなかった `split` メソッドに関するバグが判明した点や、引き分けの場合の `None` に関する `judge` メソッドの返り値の問題点など、想定外の説明を行う必要がでたため、テストの残りは次回に続くことにします。

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

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

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

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

https://github.com/ysgeso/marubatsu/blob/master/031/marubatsu_new.py

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

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

# 次回の記事

https://qiita.com/ysgeso/items/24a604d82249f61e199d

[^1]:例えば、関数を __更新した後__ で、その関数の __説明の更新を忘れる__ ことは __良くある__ ことです
[^2]:`type` については、[前回の記事](https://qiita.com/ysgeso/items/1a8a4319ef3c896ef271#%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92-for-%E6%96%87%E3%81%AB%E8%A8%98%E8%BF%B0%E3%81%97%E3%81%9F%E9%9A%9B%E3%81%AE%E6%B3%A8%E6%84%8F%E7%82%B9) を参照して下さい