目次と前回の記事
実装の進捗状況と前回までのおさらい
〇×ゲームの仕様と進捗状況
正方形で区切られた 3 x 3 の 2 次元のゲーム盤上でゲームを行う
ゲーム開始時には、ゲーム盤のすべてのマスは空になっている
2 人のプレイヤーが遊ぶゲームであり、一人は 〇 を、もう一人は × のマークを受け持つ
2 人のプレイヤーは、交互に空いている好きなマスに自分のマークを 1 つ置く
先手は 〇 のプレイヤーである
- プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する
- すべてのマスが埋まった時にゲームの決着がついていない場合は引き分けとする
仕様の進捗状況は、以下のように表記します。
- 実装が完了した部分を
背景が灰色の長方形
で記述する - 実装の一部が完了した部分を、太字 で記述する
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
前回までのおさらい
前回の記事では、Marubatsu
クラスの judge
メソッドに対して、改良条件判断網羅(以後は MC/DC と表記します)でのテストを開始しました。具体的には、〇 の勝利の判定を行う if 文に対するテストケースを作成し、テストを行いました。
今回の記事ではその続きを行います。
改良条件判断網羅(MC/DC)によるテストの続き
前回の記事で、〇 の勝利の判定を行う if 文に対するテストを行ったので、次は、× の勝利の判定 を行う if 文に対するテストを行います。なお、表記が長いので、以降は「× の勝利の判定を行う if 文に対するテストケース」を、× の勝利のテストケース のように表記します。
× が勝利した場合のテストケース
下記は、judge
メソッドの中で、× の勝利を判定する if 文です。
# × の勝利の判定
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[0][2] == Marubatsu.CROSS:
winner = Marubatsu.CROSS
このプログラムは、〇 の勝利を判定 する if 文と 同様 に、条件式は、or 演算子のみ で 連結 されています。従って、この if 文に対する MC/DC のテストケースは、〇 の勝利のテストケースと 同様 に、下記の性質を満たす ようなテストケースを用意すれば良いことがわかります。
-
or 演算子 で 連結 された 式 の計算結果が すべて
False
になる テストケース -
or 演算子 で 連結 された 式 の計算結果のうち、1 つだけ が
True
になる テストケース
すべてが False
になるテストケース
上記のうち、すべてが False
になるテストケースは、〇 の勝利のテストケース として用意した、ゲーム盤に一つも マークが配置されていない テストケースを そのまま利用 することができます。従って、すべてが False
になるテストケースを 新しく作成 する 必要はありません。
1 つだけが True
になるテストケース
× の勝利を判定する if 文の条件式の中の、or 演算子で連結された それぞれ の 式が True
になるのは、下図 のマスに × が配置された場合 です。下図の 8 通り の いずれの場合 でも、同時 に 2 つ以上 の場所で × が 3 つ並ぶことはない ので、1 つだけ が True
になる ことが 保証 されます。従って、下図 のように × が配置 された、8 つ のテストケースを 用意すれば良い ことがわかります。
次に、その 8 つのテストケース に対する judge
メソッド の 期待される返り値 について考える必要があります。ここまでの説明は、前回の記事 の、〇 が勝利した場合 と 同様 なので、この後の説明も同じような説明になると思う人がいるかもしれませんが、ここから の説明は 異なります。
× を 3 つ配置 するということは、その前 に 〇 を 3 回配置 することになります。従って、下図のように、〇 が 3 つ並んで配置 するようなテストケースを作成しても、× の勝利を判定する if 文の条件式の 1 つだけ が True
になる ことに変わりはありません。
ただし、下記の 〇×ゲームの 仕様 6 で、同じマークが 3 つ並んだ場合 は、ゲームが終了 することが決められています。従って、上記のような 〇 と × が 同時に 3 つ並ぶ ような 状況 が起きることは あり得ない ことがわかります。
「プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する」
実際 の 〇×ゲームで 出現しない ようなゲーム盤の状況を表す テストケース を作成してテストを行っても 意味はない ので、× の勝利のテストケースは、〇 が勝利していない状態 で 作成する必要 があります。
× の勝利のテストケースでは、着手 は 〇 の 3 回、× の 3 回 の、計 6 回 になるので、引き分け になることは ありません。従って、引き分けを判定 する if 文の 条件式 の計算結果も 必ず False
になります。
下図は、上記の 条件を満たす テストケースに対する judge
メソッド の 処理の流れ を表す フローチャート で、赤い枠線と線 が行われる 処理の流れ を表します。先ほど説明したように、用意するテストケースは、〇 が勝利しない ように作られるので、〇 の勝利を判定 する if 文の条件式の計算結果は 常に False
になります。図から、winner
に関する 代入処理 は、赤字 の winner = Marubatsu.CROSS
が 最後に行われる ので、judge
メソッドの 期待される返り値 は Marubatsu.CROSS
であることがわかります。
下記は、上記の条件を満たす 8 つの テストケースを記述したプログラムです。なお、前回の記事で記述した、〇 の勝利 のテストケースを 記述しない理由 は 後述 します。
from marubatsu import Marubatsu
testcases = {
# × の勝利のテストケース
Marubatsu.CROSS: [
"A2,A1,B2,B1,A3,C1",
"A1,A2,B1,B2,A3,C2",
"A1,A3,B1,B3,A2,C3",
"B1,A1,B2,A2,C1,A3",
"A1,B1,A2,B2,C1,B3",
"A1,C1,A2,C2,B1,C3",
"A2,A1,A3,B2,B1,C3",
"A1,C1,B1,B2,A2,A3",
],
}
次に、このテストケースに対して test_judge
を実行 してテストを行います。まず、実行結果を見て、正しいマス にマークが 配置 されていることを 必ず確認 して下さい。
from test import test_judge
test_judge(testcases)
実行結果
Turn o
xxx
oo.
o..
ok
Turn o
oo.
xxx
o..
ok
Turn o
oo.
o..
xxx
ok
Turn o
xoo
xo.
x..
ok
Turn o
oxo
ox.
.x.
ok
Turn o
oox
o.x
..x
ok
Turn o
xo.
ox.
o.x
ok
Turn o
oox
ox.
x..
test_judge error!
mb.judge(): playing
winner: x
実行結果に 表示される、8 つのテストケースによって着手が行われた ゲーム盤 から、正しいマス にマークが 配置 されていることが 確認 できます。
次に、judge
メソッド が、期待される仮り値 を返しているかどうかを 確認 します。下記の 実行結果 の 最後の部分 から、最後のテストケース に対して、mb.judge()
の返り値 が、期待される返り値 を表す winner
に代入された "x"
とは 異なる、"playing"
になっていることがわかります。
略
Turn o
oox
ox.
x..
test_judge error!
mb.judge(): playing
winner: x
このエラーメッセージが表示されるのは、mb.judge()
の返り値と winner
に代入された値の どちらか、または 両方 が 間違っている ことが原因なので、その点について検証します。
このテストケースは、(2, 0)、(1, 1)、(0, 2) のマスに × が並んでいるため、× が勝利 しています。従って、期待される返り値を表す winner
が "x"
である ことは 正しい です。従って、mb.judge()
の返り値 が、ゲームが決着していない ことを表す "playing"
になっている点が 間違っています。
このことから judge
メソッド の、× の勝利を判定 する if 文の条件式の中で、(2, 0)、(1, 1)、(0, 2) のマスに × が配置される ことを判定する部分にバグが存在する 可能性が高い ことがわかります。
judge
メソッドの その部分の条件式 を調べてみると、下記のプログラムのように、(2, 0)、(1, 1)、(2, 2) という 間違ったマス を 判定 していることがわかります。
self.board[2][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CROSS:
従って、この部分を下記のプログラムのように、正しいマスを調べる ように 修正 することでこの バグを修正 することができます。
self.board[2][0] == self.board[1][1] == self.board[0][2] == Marubatsu.CROSS:
修正箇所
- self.board[2][0] == self.board[1][1] == self.board[2][2] == Marubatsu.CROSS:
+ self.board[2][0] == self.board[1][1] == self.board[0][2] == Marubatsu.CROSS:
上記以外の部分は修正していないので、修正した judge
メソッドの全体をこの記事に記述しませんが(github のほうには記述して実行します)、修正後に下記のプログラムを実行することで、最後のテストケースで エラーメッセージ が 表示されなくなり、judge
メソッドの バグが修正 されたことが 確認 できます。なお、実行結果は、最後の部分のみを記述します。
先程と同じなので省略
Turn o
oox
ox.
x..
ok
これが、judge
メソッドの、3 つあるバグの中の 1 つ目のバグ です。このバグのような、データの入力ミス が 原因 となるバグは よくある事 です。特に、judge
メソッドのように、大量のデータを入力 する必要があるプログラムでは、頻発する ので 注意が必要 です。
このように、MC/DC テスト を行うことで、judge
メソッドに バグ が存在することを 発見 し、エラーメッセージからその 原因を特定 して 修正 することができました。
test_judge
の改良
上記で、× の勝利を判定する if 文のテストケースの作成が終了しましたが、現状の test_judge
には いくつかの問題 があるので、次に行く前に 改良 を行うことにします。
× の勝利のテストケースのみを記述した理由
先程、× の勝利 のテストケースを 記述 する際に、前回の記事で作成した 〇 の勝利 のテストケースを 記述しなかった理由 について説明します。
下記は、〇 の勝利 のテストケースを 含めた プログラムです。
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",
],
# × の勝利のテストケース
Marubatsu.CROSS: [
"A2,A1,B2,B1,A3,C1",
"A1,A2,B1,B2,A3,C2",
"A1,A3,B1,B3,A2,C3",
"B1,A1,B2,A2,C1,A3",
"A1,B1,A2,B2,C1,B3",
"A1,C1,A2,C2,B1,C3",
"A2,A1,A3,B2,B1,C3",
"A1,C1,B1,B2,A2,A3",
],
}
このテストケースに対して test_judge
で テストを行う と、下記の実行結果のように、すべてのテストケース に対する ゲーム盤の表示 が行われるため、非常に長い表示 が行われてしまします。
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
Turn o
xxx
oo.
o..
ok
Turn o
oo.
xxx
o..
ok
Turn o
oo.
o..
xxx
ok
Turn o
xoo
xo.
x..
ok
Turn o
oxo
ox.
.x.
ok
Turn o
oox
o.x
..x
ok
Turn o
xo.
ox.
o.x
ok
Turn o
oox
ox.
x..
ok
JupyterLab では、一定以上の行数 の長い 表示 を行うようなプログラムを実行した場合に、表示の一部 のみが表示され、その後 で 下記のような表示 が行われる場合があります。
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
最初の文は、出力(output)(表示の事)が 途中 で 切り捨てられている(truncated)という意味です。その後の scrollable element
の部分は リンク になっており、クリック することで、スクロール可能 (scrollable)な セル に 表示が行われる ようになります。
また、その後の text editor
のリンクを クリック することで、VSCode の 新しいタブ に、(文字のみを扱う)テキストエディタ として 結果が表示 されるようになります。
プログラムの実行結果を、切り捨てず に 何行まで表示 するかは、VSCode の 設定で変更 することができます。最後に表示される settings
のリンクを クリック することで、その設定を行う画面が VSCode の 新しいタブ に表示されます。変更したい人は、Notebook Output Text Limit の項目が表示する行数を表すので、その部分を修正して下さい。
× の勝利 のテストケースを 新しく記述 して テストを行う 際に、上記 のような 実行結果が表示 された場合の事を 考えてみて下さい。上記の実行結果で表示されたゲーム盤のうち、新しく追加 した × の勝利 のテストケース 以外 の ゲーム盤の表示 は、既に 前回の記事で マークの配置 を 確認済 なので、表示 する 必要はありません。
先程、× の勝利 のテストケース のみを記述 してテストを行った理由は、新しく記述 したテストケースに対して のみ、正しいマス に マークが配置 されていることを 確認 するためです。
テストケースを 新しく作成 した場合は、その テストケースだけ で、test_judge
を実行 して、テストケースによって 正しいマスにマークが配置 されることを 確認 すると良いでしょう。
テストケースの管理方法
上記の説明を読んだ人で、テストケースを追加する際に、全て のテストケースを まとめて記述 するの ではなく、テストケースを 追加するたび に、異なる変数 を用意して、その変数に 追加する テストケースを記述したものを 代入 したほうが良いのではないかと思った人はいないでしょうか?
もちろん、複数の変数 でテストケースを 管理 してテストを行うことは可能ですが、そのような方法は、「変数の管理 が 大変 になる」、「テストを行う際に、一部 のテストケースを使うことを 忘れてしまう 可能性が生じる」などの 欠点 があるため、あまりお勧めしません。
また、一度 テストを行って、judge
メソッドが 期待される処理を行う ことが 確認 されたテストケースは、二度と利用する必要 がないので、複数の変数 でテストケースを 管理しても良い のではないかと 思う人がいるかもしれません が、それは 大きな間違 いです。
これまでの記事で何度も行ってきたように、一度 定義した関数 を、後から修正 することは良くある事です。関数を修正 した場合は、当然ですが、修正した関数が 正しく動作 するかを テストする必要 があります。全てのテストケースを まとめて記述 し、testcases
のような 変数に代入 ておくことで、テストの やり直し を test_judge(testcases)
を 呼び出すだけ で 簡単に実行 できます。
一方、全て のテストケースを まとめて記述 するという方法では、test_judge
で テストを行うたび に、すべて のテストケースに対応する ゲーム盤が表示 されてしまうという 問題 があります。ただし、この問題は、test_judge
を 改良 することで 簡単に解決 することができます。
test_judge
の改良 その 1(ゲーム盤の表示の有無の選択)
具体的には、test_judge
に、ゲーム盤 を 表示するどうか を表す 仮引数を追加 するという方法です。この方法では、仮引数の値 が True
の場合 のみ 、test_judge
で ゲーム盤を表示 します。
このような目的の 仮引数の名前 として、ゲーム盤の表示 が、正しいマスにマークが配置されていないという バグを発見 するという、デバッグ のための 表示 であることから debug
という名前が 良く使われる1ので、本記事でもこの名前にします。また、test_judge
で ゲーム盤を表示 する 必要がある のは、テストケース を 新しく記述 した 場合だけ なので、この仮引数を デフォルト引数 とし、デフォルト値 を False
とします。
下記は、test_judge
を修正したプログラムです。修正箇所 は、仮引数 debug
の追加 と、10
行目に if 文 を記述し、debug
が True
の場合 のみ ゲーム盤を表示 するようにした点です。
1 from test import excel_to_xy
2
3 def test_judge(testcases, debug=False):
4 for winner, testdata_list in testcases.items():
5 for testdata in testdata_list:
6 mb = Marubatsu()
7 for coord in [] if testdata == "" else testdata.split(","):
8 x, y = excel_to_xy(coord)
9 mb.move(x, y)
10 if debug:
11 print(mb)
12
13 if mb.judge() == winner:
14 print("ok")
15 else:
16 print("test_judge error!")
17 print("mb.judge():", mb.judge())
18 print("winner: ", winner)
行番号のないプログラム
from test import excel_to_xy
def test_judge(testcases, debug=False):
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)
if debug:
print(mb)
if mb.judge() == winner:
print("ok")
else:
print("test_judge error!")
print("mb.judge():", mb.judge())
print("winner: ", winner)
修正箇所
from test import excel_to_xy
-def test_judge(testcases):
+def test_judge(testcases, debug=False):
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 debug:
+ print(mb)
if mb.judge() == winner:
print("ok")
else:
print("test_judge error!")
print("mb.judge():", mb.judge())
print("winner: ", winner)
下記のプログラムのように、2 つ目 の 実引数 に True
を記述 して test_judge
を呼び出すことで、ゲーム盤を表示 したテストが行われます。実行結果は先ほどと同じなので省略します。
test_judge(testcases, True)
2 つ目の実引数 が何を意味するかが 分かりにくい と思った方は、下記のように、キーワード引数 を使って、記述すると良いでしょう。本記事でもわかりやすさを重視してこのように記述します。
test_judge(testcases, debug=True)
debug
は デフォルト値 に False
が設定 された デフォルト引数 なので、下記のように debug
に対応する 実引数を省略 することで、ゲーム盤を表示せず に テストを行う ことができます。
test_judge(testcases)
実行結果
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
ok
test_judge
の改良 その 2(期待された処理が行われた場合の表示)
先程の実行結果では、judge
メソッドが 期待された値を返した ことを表す "ok"
が 大量に表示 されましたが、テストで 知りたいこと は、以前の記事 で説明したように、期待された処理 が 行われなかった テストケースが あるかどうかを調べること なので、期待された処理が行われたことを表す "ok"
を表示しても あまり意味はありません。また、テストでは、一般的に ほとんど のテストケースに対して 期待された処理が行われる ので、そのことを 画面に表示 すると、上記のように画面が メッセージで埋め尽くされる ことになり、肝心 の エラーメッセージ が 埋もれて わからなくなってしまう 可能性が高く なります。
そこで、下記のプログラムのように、test_judge
で 期待された処理 が 行われた場合 は、メッセージ を 表示しない ように 修正 することにします。具体的には、12 行目の print("ok")
を ブロックの中 で 何の処理も行わない ことを表す pass
に修正 します。
1 def test_judge(testcases, debug=False):
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 if debug:
9 print(mb)
10
11 if mb.judge() == winner:
12 pass
13 else:
14 print("test_judge error!")
15 print("mb.judge():", mb.judge())
16 print("winner: ", winner)
行番号のないプログラム
def test_judge(testcases, debug=False):
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)
if debug:
print(mb)
if mb.judge() == winner:
pass
else:
print("test_judge error!")
print("mb.judge():", mb.judge())
print("winner: ", winner)
修正箇所
def test_judge(testcases, debug=False):
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)
if debug:
print(mb)
if mb.judge() == winner:
- print("ok")
+ pass
else:
print("test_judge error!")
print("mb.judge():", mb.judge())
print("winner: ", winner)
修正後に、test_judge
を実行すると、下記のように、何も表示されなくなります。
test_judge(testcases)
実行結果
ブロックの中で何の処理も行わない場合は、必ず pass
を記述 する 必要 があり、pass
を 省略 すると、下記のプログラムのように エラーが発生 します。
if True: # この if 文のブロックが記述されていない
a = 1
実行結果
Cell In[13], line 2
a = 1
^
IndentationError: expected an indented block after 'if' statement on line 1
上記のエラーメッセージは、以下のような意味を持ちます。
-
IndentationError
インデント(indent)に関するエラー -
expected an indented block
インデントされた(indented)ブロック(block)が期待されている(expected)
pass がわかりにくいと思った人は、下記のように、judge
メソッドの返り値と、期待される返り値が 等しくない 場合にエラーメッセージを表示するようにすると良いでしょう。
if mb.judge() != winner:
print("test_judge error!")
print("mb.judge():", mb.judge())
print("winner: ", winner)
修正箇所
- if mb.judge() == winner:
- pass
- else:
+ if mb.judge() != winner:
print("test_judge error!")
print("mb.judge():", mb.judge())
print("winner: ", winner)
test_judge
の改良 その 3(途中経過の表示)
先程の改良によって、すべてのテストケースで judge
メソッドが期待された値を返す場合は、何も 表示が行われなく なりますが、何かの処理を行った結果、何も表示されない と、うまくいったかどうかがわからないので、不安になる 人が多いのではないかと思います。
そのような場合は、途中経過 を表す メッセージを表示 するのが一般的です。
どのようなメッセージが わかりやすい かについては、人によって違う ので唯一の正解はありません。本記事では、下記のようなメッセージを表示することにしますが、もっと良いメッセージを思いついた人は、自分なりに自由にアレンジして下さい。
- テストを開始 したことを表示する(2 行目)
- 期待される返り値 が 変わった時 に、その値 を表示する(4 行目)
-
judge
メソッドの返り値が 期待される返り値の場合 に、そのことを 短く表示 する(14 行目) - テストを終了 したことを表示する(20 行目)
下記は、上記のメッセージを表示するように修正したプログラムです。上記の()の中は、それぞれのメッセージを表示するプログラムを記述した行数を表します。
1 def test_judge(testcases, debug=False):
2 print("Start")
3 for winner, testdata_list in testcases.items():
4 print("test winner =", winner)
5 for testdata in testdata_list:
6 mb = Marubatsu()
7 for coord in [] if testdata == "" else testdata.split(","):
8 x, y = excel_to_xy(coord)
9 mb.move(x, y)
10 if debug:
11 print(mb)
12
13 if mb.judge() == winner:
14 print("o", end="")
15 else:
16 print("test_judge error!")
17 print("mb.judge():", mb.judge())
18 print("winner: ", winner)
19 print()
20 print("Finished")
行番号のないプログラム
def test_judge(testcases, debug=False):
print("Start")
for winner, testdata_list in testcases.items():
print("test winner =", winner)
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)
if debug:
print(mb)
if mb.judge() == winner:
print("o", end="")
else:
print("test_judge error!")
print("mb.judge():", mb.judge())
print("winner: ", winner)
print()
print("Finished")
修正箇所
def test_judge(testcases, debug=False):
print("Start")
for winner, testdata_list in testcases.items():
+ print("test winner =", winner)
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)
if debug:
print(mb)
if mb.judge() == winner:
- pass
+ print("o", end="")
else:
print("test_judge error!")
print("mb.judge():", mb.judge())
print("winner: ", winner)
+ print()
+ print("Finished")
テストケースの数 があまり 多くない 場合は、上記の 14 行目のように、judge
メソッドの返り値が期待される返り値であった場合に、"o"
のような 短いメッセージを表示 することで、テストが行われていることを 実感できるようにする ことができます。ただし、テストケースの数 が、例えば 10000 のように 非常に多い 場合は、このようなメッセージは 表示しないほうが良い でしょう。なお、print
で "o"
を 改行せずに表示 するために、end=""
を実引数に記述しています。
19 行目の print()
は、3 行目の for 文の 次の繰り返し を 行う前 に、改行を行う ために記述しています。これを 記述しない と、14 行目で表示した "o"
の直後 に 4 行目の print
のメッセージが表示 されてしまいます。具体例はこの後のノートを参照して下さい。
修正後に、test_judge
を実行すると、下記のようなメッセージが表示されるようになります。test winner =
の次の行の "o"
の数 は、期待される返り値 に 対応するテストケースの数 です。
test_judge(testcases)
実行結果
Start
test winner = playing
o
test winner = o
oooooooo
test winner = x
oooooooo
Finished
19 行目の print()
を記述しないと、実行結果は以下のように、test winner
の前 などに "o"
が表示 されるようになります。興味がある方は、実際に 19 行目を削除して test_judge
を実行してみて下さい。
Start
test winner = playing
otest winner = o
ooooooootest winner = x
ooooooooFinished
test_judge
の改良 その 3 の問題点
先程実行した、test_judge(testcases)
では、テストケースの中に、下記の if 文 の 条件式 が False
になる ようなものが 存在しない ため、else
のブロック が 一度も実行されていません でした。そのため、改良後 の test_judge
で、下記の if 文の条件式が False
になった場合 にどのような メッセージが表示 されるかを 確認 しておいたほうが良いでしょう。
これは、すべての文 を 1 度は実行する という、命令網羅(C0) のテストを行うということです。
if mb.judge() == winner:
print("o", end="")
else:
print("test_judge error!")
print("mb.judge():", mb.judge())
print("winner: ", winner)
この if 文の条件式が False
になるのは、judge
メソッドが 期待される返り値 を 返さない 場合です。確認の方法 として、judge
メソッド を バグのある状態に戻す という方法が考えられますが、それよりも、テストケースのデータ のほうを 間違ったデータに修正 したほうが 簡単 です。
そこで、テストケースを下記のように、わざと間違ったデータ に 修正 します。具体的には、下記のプログラムの、コメントが書かれている行のテストデータの最後に記述されていた "B3"
を削除 することで、〇 が勝利していない テストデータに修正しています。なお、このデータは正しいデータが代入された testcases
と 区別ができる ように、testcases_error
という変数に代入します。
testcases_error = {
# ゲーム盤に一つもマークが配置されていない場合のテストケース
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",
],
# × の勝利のテストケース
Marubatsu.CROSS: [
"A2,A1,B2,B1,A3,C1",
"A1,A2,B1,B2,A3,C2",
"A1,A3,B1,B3,A2,C3",
"B1,A1,B2,A2,C1,A3",
"A1,B1,A2,B2,C1,B3",
"A1,C1,A2,C2,B1,C3",
"A2,A1,A3,B2,B1,C3",
"A1,C1,B1,B2,A2,A3",
],
}
修正後に、test_judge
を実行すると、下記のようなメッセージが表示されます。このエラーメッセージが わかりにくい 主な 理由 が 2 つあるので、その理由について少し考えてみて下さい。
test_judge(testcases_error)
実行結果
Start
test winner = playing
o
test winner = o
ooootest_judge error!
mb.judge(): playing
winner: o
ooo
test winner = x
oooooooo
Finished
メッセージがわかりにくい 主な理由 は以下の 2 点です。
-
ooootest_judge error!
の行で、oooo
とtest_judge error!
の間で 改行されていない - エラーメッセージの前に、ゲーム盤 が 表示されていない ので、どのような状況 でエラーが発生したかが わからない
一つ目は、test_judge error!
の 直前で改行 を表示することで解決できます(16 行目)。
二つ目は、test_judge error!
の 後で 、ゲーム盤を表示 することで解決できます(18 行目)。
下記は、そのように修正したプログラムです。上記の()の中が修正箇所を表します。
1 def test_judge(testcases, debug=False):
2 print("Start")
3 for winner, testdata_list in testcases.items():
4 print("test winner =", winner)
5 for testdata in testdata_list:
6 mb = Marubatsu()
7 for coord in [] if testdata == "" else testdata.split(","):
8 x, y = excel_to_xy(coord)
9 mb.move(x, y)
10 if debug:
11 print(mb)
12
13 if mb.judge() == winner:
14 print("o", end="")
15 else:
16 print()
17 print("test_judge error!")
18 print(mb)
19 print("mb.judge():", mb.judge())
20 print("winner: ", winner)
21 print()
22 print("Finished")
行番号のないプログラム
def test_judge(testcases, debug=False):
print("Start")
for winner, testdata_list in testcases.items():
print("test winner =", winner)
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)
if debug:
print(mb)
if mb.judge() == winner:
print("o", end="")
else:
print()
print("test_judge error!")
print(mb)
print("mb.judge():", mb.judge())
print("winner: ", winner)
print()
print("Finished")
修正箇所
def test_judge(testcases, debug=False):
print("Start")
for winner, testdata_list in testcases.items():
print("test winner =", winner)
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)
if debug:
print(mb)
if mb.judge() == winner:
print("o", end="")
else:
+ print()
print("test_judge error!")
+ print(mb)
print("mb.judge():", mb.judge())
print("winner: ", winner)
print()
print("Finished")
修正後に、test_judge
を実行すると、下記のようなメッセージが表示されます。
test_judge(testcases_error)
実行結果
Start
test winner = playing
o
test winner = o
oooo
test_judge error!
Turn o
xo.
xo.
...
mb.judge(): playing
winner: o
ooo
test winner = x
oooooooo
Finished
修正後のメッセージは、修正前より は わかりやすく なっていますが、エラーメッセージ と そうでないメッセージ の 区別がつきづらい 点がまだ わかりづらい のではないかと思います。
このような場合は、エラーメッセージ の 前後 で 改行 したり、下記のプログラムの 17、22 行目のように、エラーメッセージの 境目 が 明確になるような表示 を行うという工夫が考えられます。
1 def test_judge(testcases, debug=False):
2 print("Start")
3 for winner, testdata_list in testcases.items():
4 print("test winner =", winner)
5 for testdata in testdata_list:
6 mb = Marubatsu()
7 for coord in [] if testdata == "" else testdata.split(","):
8 x, y = excel_to_xy(coord)
9 mb.move(x, y)
10 if debug:
11 print(mb)
12
13 if mb.judge() == winner:
14 print("o", end="")
15 else:
16 print()
17 print("====================")
18 print("test_judge error!")
19 print(mb)
20 print("mb.judge():", mb.judge())
21 print("winner: ", winner)
22 print("====================")
23 print()
24 print("Finished")
行番号のないプログラム
def test_judge(testcases, debug=False):
print("Start")
for winner, testdata_list in testcases.items():
print("test winner =", winner)
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)
if debug:
print(mb)
if mb.judge() == winner:
print("o", end="")
else:
print()
print("====================")
print("test_judge error!")
print(mb)
print("mb.judge():", mb.judge())
print("winner: ", winner)
print("====================")
print()
print("Finished")
修正箇所
def test_judge(testcases, debug=False):
print("Start")
for winner, testdata_list in testcases.items():
print("test winner =", winner)
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)
if debug:
print(mb)
if mb.judge() == winner:
print("o", end="")
else:
print()
+ print("====================")
print("test_judge error!")
print(mb)
print("mb.judge():", mb.judge())
print("winner: ", winner)
+ print("====================")
print()
print("Finished")
修正後に、test_judge
を実行すると、下記のようなメッセージが表示されます。
test_judge(testcases_error)
実行結果
Start
test winner = playing
o
test winner = o
oooo
====================
test_judge error!
Turn o
xo.
xo.
...
mb.judge(): playing
winner: o
====================
ooo
test winner = x
oooooooo
Finished
ずいぶんとわかりやすくなったと思いますので、本記事ではこれを採用することにします。
上記で満足できない方は、自分がわかりやすいと思えるようになるまで、自由に修正して下さい。例えば、期待される返り値が変わった時に表示する、test winner = o
の前で改行するなどの工夫が考えられるでしょう。
上記の 16 行目のプログラムは改行を表示するプログラムですが、改行を表す \n
という エスケープシーケンス を使うことで、下記の 2 行をまとめる ことが出来ます。
16 print()
17 print("====================")
具体的には下記のように記述します。
print("\n====================")
修正箇所
-print()
-print("====================")
+print("\n====================")
今回の記事のまとめ
今回の記事では、× の勝利 を判定する if 文に対する MC/DC のテストを行い、test_judge
のバグ の一つを 発見 し、修正 しました。また、test_judge
を改良 しました。
大変申し訳ありませんが、test_judge
の改良が思いのほか長くなったので今回の記事はここまでにしたいと思います。次回で、テストに関する記事を終了したいと思います。
本記事で入力したプログラム
以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。
以下のリンクは、今回の記事で更新した marubatsu.py です。
以下のリンクは、今回の記事で更新した test.py です。
次回の記事
-
他にも、詳細という意味を表す
verbose
という名前が使われるようです ↩