Python
TDD
test
testing

PythonでProperty-based Testing

PythonでProperty-based TestingをするためのライブラリHypothesisのインストールと基本的な使い方を説明します。
日本語の情報が見つけられなかったので、こちらのページを参考に、備忘録がてら書いておきます。
ちなみに、「hypothesis」は、日本語で「仮説」という意味らしいです。

インストール

pipでインストールできます。

hypothesisのインストール
$ pip install hypothesis


サンプルコード内でnoseも使用してるので、noseもついでにインストールしてください。

noseのインストール
$ pip install nose

使用したバージョンはそれぞれ以下になります。

ツール バージョン
Python 3.6.2
pip 9.0.1
hypothesis 3.30.2
nose 1.3.7

使い方

Hypothesisを使ったサンプルコードが以下になります。

example.py
from nose.tools import raises
from hypothesis import given
from hypothesis.strategies import integers


def division(x, y):
    return float(x) / float(y)


@given(x=integers())
def test_div(x):
    """整数値除算のテスト"""
    print(x)  # エラー時にログ表示
    assert division(x * 2, x) == 2.0


@raises(ZeroDivisionError)
def test_divzero():
    """0除算のテスト"""
    division('4', 0)

「given」というデコレータで、テストメソッドの引数の値を指定します。
ここでは x=integers() とすることで、xの値を整数値のPropertyでテストすることができます。

Hypothesisの箇所
@given(x=integers())
def test_div(x):
    """Test integers."""
    print(x)  # エラー時にログ表示
    assert division(x * 2, x) == 2.0

テスト実行(エラー発生)

上記のコード(example.py)をテスト実行します。

テスト実行
$ nosetests example.py
E.
======================================================================
ERROR: Test integers.
----------------------------------------------------------------------
Traceback (most recent call last):
[...]
ZeroDivisionError: float division by zero
-------------------- >> begin captured stdout << ---------------------
102860293121396009173326105558127297308
55485753836579463694521216132454618744
55485753836579463694521216132454618624
62718710765820030520700417840365121327
62718710765820030520700417840365121280
121434099567864314412419957946238851931
-22018270800766606459394827539702648976
-22018270800766606459394827539702648832
-22018270800766606459394827539702648833
169012129506219865175533834052023541029
169012129506219865175533834052023540992
0
0
Falsifying example: test_div(x=0)
0
--------------------- >> end captured stdout << ----------------------

----------------------------------------------------------------------
Ran 2 tests in 0.066s

FAILED (errors=1)

一部省略してますが、上記のようなエラーが発生するはずです。
capturedを見てもらえればわかるように、複数の値でテストを実行してくれてます。

ここでエラーとなっているのは、テストケースの入力値として想定しない 0 がテストに含まれていることが原因です。

テストケースの修正(assume関数)

0 をテストケースから除外するために、assume関数を使用します。
修正版のコードが以下になります。

エラー修正版(example.py)
from nose.tools import raises
from hypothesis import given
from hypothesis import assume
from hypothesis.strategies import integers


def division(x, y):
    return float(x) / float(y)


@given(x=integers())
def test_div(x):
    """整数値除算のテスト"""
    assume(x != 0)  # 0はテスト対象から除外する

    print(x)  # エラー時にログ表示
    assert division(x * 2, x) == 2.0


@raises(ZeroDivisionError)
def test_divzero():
    """0除算のテスト"""
    division('4', 0)

assume(x != 0) でテスト条件を設定しています。
ここでは、0はテストケースに含まないように指定しています。

テスト実行

再度、テストを実行します。

テスト再実行
$ nosetests example.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.490s

OK

今回は、成功しました。

以上

こんな感じで、PythonでもProperty-based Testingをすることができます。
また、指定するデータのパターンはここに一覧があるので、テストケースに合わせて使用してください。