PyQCheckというPythonでQuickCheckが行えるライブラリをPyPIに登録した。

# PyQCheck

昨年の8月末に公開した[PyQCheck](https://github.com/futoase/PyQCheck)。
ずーっとPython 2.7向けにリリースして、且つsetup.pyを作って
リポジトリに放っておいている状態だったので、
勿体ないと思ったので、以下の対応をした。

- Python 3.3対応 (それ以前の3.1, 3.2及び2.x系は保証外)
- PyPIへの登録(インストールの簡略化)
- 細かいところを若干対応(自分としてはアレな対応だと思う__init__.pyへのコード移植 -> ディレクトリimport対策...)

貧弱な機能しか無いけれど、そこそこ使えると思う。

# 作成した経緯

細かくは忘れたけど、[QuickCheck](http://itpro.nikkeibp.co.jp/article/COLUMN/20080304/295346/)というライブラリが某所のごく僅かな範囲で盛り上がり、
得意言語を別々にする人たちがそれぞれの得意・好きなプログラミング言語にてQuickCheck風の
テストを行えるようにするフレームワークを作った、というのがきっかけだったと思う。

# 何をするものなのか

簡単にいうと、以下のような動きをする

- 引数を取る関数を自分で作る。
- その引数に対し処理を行える範囲を保証している、型を保証している値を入れ実行する
- 関数から返された値がTrueかFalseか、例外吐いたかで判断する

# とりあえず使ってみよう
## 動作環境

動作環境として、以下の条件がある

- Python 3.3 以上をインストール済みであること

## インストール

以下のコマンドでインストールが可能です。
...システム環境汚染を防ぐため、pyvenvを利用してインストール環境を整える。
distributeを落としてeasy_installを入れてインストールの準備とか。

```sh
> mkdir ~/Sandbox/PyQCheck
> cd ~/Sandbox/PyQCheck
> pyvenv .venv
> source .venv/bin/activate
> curl -O http://python-distribute.org/distribute_setup.py
> python distribute_setup.py
> easy_install PyQCheck
```

以上で準備おわり。

# 動作をみてみる

以下に動作サンプルのためのコードスニペットを添付。

```py3:equal_length.py
# -*- coding:utf-8 -*-

from pyqcheck import PyQCheck, Arbitrary

def equal_length(a, b):
  return len(a) == len(b)

PyQCheck(verbose=True).add(
  Arbitrary(
    ('string', dict(min=10, max=10)), 
    ('string', dict(min=10, max=10))
  ).property(
    'len(a) == len(b)', equal_length
  )
).run(10).result()
```

## 実行

実行させてみると、以下の形で出力される。

![pyqcheck-1.png](https://qiita-image-store.s3.amazonaws.com/0/3985/626ed7f0-bbb1-fc9e-fc56-8935c9723bd8.png)

## 何をやっているか

1. Arbitraryが2つの引数を取る関数に対し長さ最小10、最大10 つまり10文字の
文字列を渡す
2. equal_lengthがa, bに文字列を渡される
3. 同じ長さの文字列なので、Trueを返す
4. 10回、テストを試行した結果、すべてOKなので☀アイコンで埋まる

# つまるところ

テスト対象となる関数に対し、関数受け取った自動引数の処理として
サポートする型、及びデータの長さなどを指定した値を適当に生成して
適当になげてテストが通るか？というチェックが行える。
100回、1000回、10000回と行なってみて、通っていればまあだいたいOKだろう、
という感じ。(QuickCheck本来の考えとはズレがあるかもしれないが...)

## 書き方を変える

デコレータを使った書き方もできる。

```py3:equal_length.py
# -*- coding:utf-8 -*-

from pyqcheck import PyQCheck, set_arbitrary

@set_arbitrary(
  ('string', dict(min=10, max=10)), 
  ('string', dict(min=10, max=10))
)
def equal_length(a, b):
  '''
  len(a) == len(b)
  '''
  return len(a) == len(b)

PyQCheck(verbose=True).run(10).result()
```

こちらの書き方のほうがスッキリすると思う...

# 変わった使い方

PyQCheckの変わった使い方としては以下の使い方もできる。
以下のスクリプトを実行しよう。
実行についてはsqlite3とのリンクを有効にした形でPython3をビルドしておかないといけない。

```py3:insert_random_value_to_the_sqlite.py
# -*- coding:utf-8 -*-

import sqlite3
from pyqcheck import PyQCheck, set_arbitrary

DB_NAME = 'test.db'

@set_arbitrary(
  ('integer', dict(min=0, max=100)),
  ('string', dict(min=3, max=10))
)
def insert_to_sqlite3(age, name):
  con = sqlite3.connect(DB_NAME)
  cur = con.cursor()
  cur.execute('''
    SELECT `name` FROM `sqlite_master` 
      WHERE `type`='table' AND name='users';
  ''')

  if cur.fetchone() is None:
    cur.execute('''
      CREATE TABLE `users` (
        `id` INTEGER NOT NULL PRIMARY KEY,
        `age` INTEGER NOT NULL,
        `name` STRING NOT NULL
      );
    ''')
    con.commit()

  cur.execute('''
    INSERT INTO `users` (`age`, `name`) 
      VALUES (?, ?)
  ''', (age, name))
  con.commit()
  
  return True

PyQCheck(verbose=True).run(30).result()
```

## 実行

実行してみる。

![pyqcheck-2.png](https://qiita-image-store.s3.amazonaws.com/0/3985/39b55807-c738-e86c-49fb-ceb65bc54007.png)

test.rbがカレントディレクトリに作成されているので、内容を見てみよう...

![pyqcheck-3.png](https://qiita-image-store.s3.amazonaws.com/0/3985/6829ef98-b26c-7535-6411-d6f8a7ee946f.png)

PyQCheckのテキトーな値を生成して関数に渡す、という性質を利用して、
ダミーデータの生成につかってみたという。
アメリカの州とか、世界の国名とか、
よく使われている姓名のデータをArbitraryとして作成してみるとか
そういうことをやるとダミーデータの生成に使えそうな気がする。

# 今後

- 利用者、ユーザーが独自定義のArbitraryを作れるようにする
- 継続的なリリースを目指す
- ファイルへの出力

PyPIにパッケージ登録したので、メンテナンスし続けておきたい。