PythonでProperty-based Testing

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

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

```bash:hypothesisのインストール
$ pip install hypothesis
```
 サンプルコード内で[nose](http://nose.readthedocs.io/)も使用してるので、noseもついでにインストールしてください。

```bash:noseのインストール
$ pip install nose
```


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

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


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


```python: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でテストすることができます。

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

## テスト実行（エラー発生）
上記のコード(example.py)をテスト実行します。

```bash:テスト実行
$ 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関数を使用します。
修正版のコードが以下になります。

```python:エラー修正版(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はテストケースに含まないように指定しています。

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

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

OK
```

今回は、成功しました。

## 以上
こんな感じで、PythonでもProperty-based Testingをすることができます。
また、指定するデータのパターンは[ここ](https://hypothesis.readthedocs.io/en/latest/data.html)に一覧があるので、テストケースに合わせて使用してください。