この記事はPython Advent Calendar 2015の14日目の記事です。
ランダムな値を返すメソッドは、結果が想定する結果よりも一定値離れていたらテスト失敗にするといった具合にして、テストすることもできますが、
統計的にテストできないかと思ってプログラムを書いてみました。
ランダムな値を返すメソッドをカイ二乗検定を用いてユニットテストします。
実装
例えばサイコロを振ってランダムに1〜6の目を返すプログラムを書くと以下のようになります。
# -*- coding: utf-8 -*-
import random
class Dice(object):
def throw(self):
return random.randint(1, 6)
これをテストするコードは以下のようになります。
# -*- 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)
scipy
のstats
モジュールが必要なのでインストールされていなければ、pip
でインストールします。
$ pip install numpy
$ pip install scipy
解説
特定の回数(今回は6000回)実行して、その実行結果を集計します。
例えば1の目が1007回、2の目が1050回・・・といったようにです。
その集計した回数とその理論値、今回であれば各目が1000回ずつ出ることとのズレを検定し、「サイコロの目の出る回数はどれも同じである」という帰無仮説が棄却できるかを調べます。
帰無仮説が棄却できる場合は、想定した結果になっていないということなので、テストが失敗します。
反対に棄却できない場合はテスト通過です。
注意点
統計的なテストにしても運が悪いと失敗してしまうのは変わらないため、実際に運用するとなると難しい面があるかもしれません。
有意水準を下げると、実際には正しいのにテストが失敗するパターンは減りますが、反対にほんの小さなズレではテストが失敗しないため、実は1の目が出る数が他より微妙に多かったというのは検出できない可能性があります。
反対に、有意水準を上げると、実際は正しいのにテストが失敗するパターンが増えます。ただ、小さなズレであっても検出することができるようにはなります。
失敗が多すぎるテストは、いつものこととして見向きもされなくなるので、実際に運用するときは有意水準は低めでやったほうがいいかなと思います。