LoginSignup
0
0

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

Last updated at Posted at 2023-11-09

目次と前回の記事

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

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

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

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

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

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

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

前回までのおさらい

前回の記事では、命令網羅(C0)judge メソッドのテストを開始しました。

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

judge メソッドのテスト

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

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

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

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

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 の中に、テストデータしか 代入されていないことが原因です。以前の記事 で説明したように、テストケースは、テストデータ と、期待される処理組み合わせ であることを思い出してください。

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

なお、前回の記事 では、『テストで利用する「特定のマスにマークが配置された Marubastu クラスのインスタンス」のことを、単に テストケース と表記します』のように説明しましたが、以後は 、上記のデータに、期待される処理を表さすデータ組み込んだもの のことを テストケース と表記することにします。同様に、テストケースを表すデータ も、期待される処理を表すデータ組み込んだもの とします。

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

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

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 の 最後の要素を削除 し、返り値として返すpop1 という list のメソッド を利用することで記述することが出来ます。

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

a = [1, 2, 3]
last = a.pop()
print(last)
print(a)

実行結果

3
[1, 2]

tuple は イミュータブル なデータなので、要素を変化 させる pop メソッドを 利用 することは できません。tuple を使ってテストケースを表すデータを表現した場合は、後述のノートで説明する方法で、test_judge を修正する必要があります。

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

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

 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!")
行番号のないプログラム
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!")
修正箇所
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!")

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

test_judge(testcase)

実行結果

Turn o
xxx
oo.
o..

ok

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

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

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

上記の修正方法の欠点

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

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] が削除 されるからです。

test_judge(testcase)

実行結果

Turn x
xx.
oo.
o..

error!

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

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

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

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

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

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!")
修正箇所
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!")

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

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']

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

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

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

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

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

  • list の 0 番の要素 に、着手を表すデータ を代入する
  • list の 1 番の要素 に、期待される judge メソッドの 返り値 の値を代入する

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

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

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 行目では、testdatawinner という変数に、testcase0 番と 1 番の要素を代入 しています。testcase中身を変更するのではなくtestdatawinner という 異なる変数testcase要素を代入 することで、pop メソッドを使った場合のように、testcase中身が変化 することは 無くなります

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

 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!")
行番号のないプログラム
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!")
修正箇所
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!")

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

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 つのデータ構造は、性質が 大きく異なります

list2 は主に、以下のような 2 種類の用途 で使われます。

  • 用途 1
    • 同じ種類任意の数のデータまとめて扱う
    • インデックスは list に代入された複数のデータの 順番 を表す
    • for 文などの 繰り返しの処理反復可能オフジェクト として記述することで、先頭の要素から順番 に取り出して 同じ種類の処理を行う ことができる
  • 用途 23
    • 異なる種類決められた数 のデータを まとめて扱う
    • インデックスは、要素に代入されたデータの 種類 を表す
    • 一般的 に、for 文などの 繰り返しの処理の反復可能オブジェクトとして 利用しない

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

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

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

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

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

引き分けの場合のテスト

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

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

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 回記述する 必要があります。このような方法では、テストケースが 増えた場合 にテストを行うのが 大変 です。

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

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

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

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 文のブロックの処理 になるように修正する
 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!")
行番号のないプログラム
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!")
修正箇所

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

- 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!")

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

test_judge(testcases)

実行結果

Turn x
ooo
xx.
...

ok
Turn o
xxx
oo.
o..

ok
Turn x
oox
xxo
oxo

ok

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

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

分岐網羅(C1) によるテストは、すべて の条件分岐の 条件式が TrueFalse で実行 されるようなテストです。

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

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

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

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

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

以前の記事で説明したように、条件網羅(C2) によるテストは、命令網羅(C0) や 分岐網羅(C1) の条件を 満たさない場合がある ので、個人的 には 中途半端な精度のテスト だと 思います。そのため、本記事では条件網羅(C2) によるテストは行いません。

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

以前の記事で説明したように、判定条件/条件網羅(CDC) によるテストは、式の値 と、条件式の値因果関係完全にチェックできない 可能性があるという 欠点 があります。そのため、本記事では、判定条件/条件網羅(CDC) によるテストは行いません。

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

複数条件網羅(MCC) によるテストは、条件分岐の 条件式の中 に、and または or論理演算子 が記述されている場合、それらで連結された それぞれの式TrueFalseすべての組み合わせ1 度は実行 されるようなテストです

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

    # 〇 の勝利の判定
    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

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

テストケースを表すデータのような、複雑なデータ構造 のデータに対する 関数の引数と返り値のデータ型のヒント簡潔に記述 することが 難しいの で、そのような場合は 関数のヒント には 何も記述せず、下記のリンク先のように、docstring のほうにデータ構造を 記述 したほうが良いでしょう。

今回の記事のまとめ

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

以前の記事 で説明したように、judge メソッドには いくつかのバグが存在 します。今回の記事では、judge メソッドに対して、命令網羅(C0) と 分岐網羅(C1) の条件を満たすテストを行いましたが、それらのテストでは バグを発見 することは できませんでした

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

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

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

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

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

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

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

次回の記事

  1. pop には「飛び出る」という意味があり、list の要素が外に飛び出て削除されることからそのような名前が付けられたという説があります

  2. tuple の用途も同様です

  3. この用途で、扱う データの種類が多い 場合は、dict を使ったほうが わかりやすくなります。そのような dict の使用方法については、次回の記事で紹介します

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0