Edited at
PythonDay 14

Pythonでランダムな値を返すメソッドのテスト

More than 3 years have passed since last update.

この記事はPython Advent Calendar 2015の14日目の記事です。

ランダムな値を返すメソッドは、結果が想定する結果よりも一定値離れていたらテスト失敗にするといった具合にして、テストすることもできますが、

統計的にテストできないかと思ってプログラムを書いてみました。

ランダムな値を返すメソッドをカイ二乗検定を用いてユニットテストします。


実装

例えばサイコロを振ってランダムに1〜6の目を返すプログラムを書くと以下のようになります。


dice.py

# -*- coding: utf-8 -*-


import random

class Dice(object):
def throw(self):
return random.randint(1, 6)


これをテストするコードは以下のようになります。


test_dice.py

# -*- coding: utf-8 -*-


import collections
import unittest

import dice

from scipy import stats

class TestDice(unittest.TestCase):

def setUp(self):
self.__target = dice.Dice()

def test_throw(self):
# 6000回実行する
result = [self.__target.throw() for n in range(0, 6000)]
# 実行結果の集計
counted = collections.Counter(result)
# イレギュラーな目が出ていないか確認
self.assertItemsEqual([1, 2, 3, 4, 5, 6], counted.keys())
# カイ二乗検定を行い、有意水準1%で偏りがないという帰無仮説を棄却できた場合にテスト失敗とする
# だいたいどの目も1000回ほど出現するはずで、そこから著しくズレていれば失敗になる
chi_square_value, p_value = stats.chisquare(
[counted[1], counted[2], counted[3], counted[4], counted[5], counted[6]],
f_exp=[1000, 1000, 1000, 1000, 1000, 1000]
)
self.assertLess(0.01, p_value)


scipystatsモジュールが必要なのでインストールされていなければ、pipでインストールします。

$ pip install numpy

$ pip install scipy


解説

特定の回数(今回は6000回)実行して、その実行結果を集計します。

例えば1の目が1007回、2の目が1050回・・・といったようにです。

その集計した回数とその理論値、今回であれば各目が1000回ずつ出ることとのズレを検定し、「サイコロの目の出る回数はどれも同じである」という帰無仮説が棄却できるかを調べます。

帰無仮説が棄却できる場合は、想定した結果になっていないということなので、テストが失敗します。

反対に棄却できない場合はテスト通過です。


注意点

統計的なテストにしても運が悪いと失敗してしまうのは変わらないため、実際に運用するとなると難しい面があるかもしれません。

有意水準を下げると、実際には正しいのにテストが失敗するパターンは減りますが、反対にほんの小さなズレではテストが失敗しないため、実は1の目が出る数が他より微妙に多かったというのは検出できない可能性があります。

反対に、有意水準を上げると、実際は正しいのにテストが失敗するパターンが増えます。ただ、小さなズレであっても検出することができるようにはなります。

失敗が多すぎるテストは、いつものこととして見向きもされなくなるので、実際に運用するときは有意水準は低めでやったほうがいいかなと思います。


参考にしたページ