noseで気軽にテストを書く(+geventの場合)


pythonのテストフレームワークはいろいろありますが、個人的には[nose](https://nose.readthedocs.org/en/latest/)というフレームワークがシンプルで好きですので、紹介したいと思います。

## noseのインストール

```
$ pip install nose
```

環境に応じてsudoが必要かもしれないですね。

## noseテストケースを書く

hogeというモジュールに以下のような関数sum, および関数is_evenがあると想定します。

```python:hoge.py
def sum(a, b):
	return a + b

def is_even(n):
	return (n % 2 == 0)
```

unittest.TestCaseを継承した以下のようなファイルtest_hoge.pyを作成します。
ここでは値が期待する**値と同じ**かどうかを調べる`eq_`と, 値が**真であるか**どうかを調べる`ok_`を`nose.tools`からインポートして使っています。
テストフレームワークによくある仕組みとして、テストの前後に初期化/リソース解放などの処理を行うための`setUp`, `tearDown`が提供されていますので、試しにprint文を入れてみます。

```python:test_hoge.py
from unittest import TestCase
from nose.tools import ok_, eq_
from hoge import sum, is_even

class HogeTestCase(TestCase):
	def setUp(self):
		print 'before test'
	def tearDown(self):
		print 'after test'
	
	def test_sum(self):
		eq_(sum(1, 2), 3)
		eq_(sum(5, 11), 16)
		eq_(sum(0, 0), 0)

	def test_is_even(self):
		ok_(is_even(2))
		ok_(not is_even(3))
```

## テストケースを実行する

noseにはnosetestsというテストランナーコマンドがついてきます。(pip install noseするとインストールされます)
ここでnosetestsを実行すると以下のようになります。


```
$ nosetests
..
----------------------------------------------------------------------
Ran 2 tests in 0.003s

OK
```

`nosetests`は実行されるとカレントディレクトリ以下からテストっぽい(testをファイル名に含む)ファイルを探し出し、`unittest.TestCase`を継承するクラスをあつめて実行してくれます。
print文で出力したはずのメッセージが出ていませんが、これはデフォルトでnoseが標準出力への出力をキャプチャしてしまうからです。
`-s`オプションを使うと出力をそのまま見ることができます。

> $ nosetests -s

実行してるテストケース名をドットだけでなく、メソッド名で表示させるときは`-v`オプションを付けます。

> $ nosetests -v

あわせて実行してみると以下のようになりました。

```
$ nosetests -s -v
test_is_even (test_hoge.HogeTestCase) ... before test
after test
ok
test_sum (test_hoge.HogeTestCase) ... before test
after test
ok

----------------------------------------------------------------------
Ran 2 tests in 0.002s

OK
```

nosetestsコマンドにはファイル名を指定して、特定のファイルのテストのみを実行させることもできます。
開発が進んできてテストが増えてくると、便利ですね。

> $ nosetests ./test_hoge.py


## その他のツール

`ok_`や`eq_`の他のテストツールをいくつか紹介します。詳しくはnoseの[Testing toolsのページ](http://nose.readthedocs.org/en/latest/testing_tools.html)を参照してください。

### raises

`raises`は例外が起きそうなテストに対してデコレータとして使います。
例外が起きそうな以下の関数`div`をテストします。

```python:fuga.py
def div(n, m):
	return n / m
```

テストケースは以下。

```python:fuga_hoge.py
from unittest import TestCase
from nose.tools import eq_, raises
from fuga import div
class FugaTestCase(TestCase):
	def test_div(self):
		eq_(div(10, 5), 2)
		eq_(div(10, 10), 1)
		eq_(div(9, 3), 3)

	@raises(ZeroDivisionError)
	def test_div_zerodiv(self):
		div(10, 0)

	
```

これでテストケース`test_div_zerodiv`がきちんと例外`ZeroDivisionError`を投げているかどうかを検査することができます。

### timed

`timed`はデコレータで、テストケースが指定の時間内(秒で指定)に終わるかどうかを検査します。
以下のような、処理に0.1秒ほど時間のかかる関数`do_something`を、0.2秒以内で終わることを検査するテストを考えます。

```python:moge.py
import time
def do_something():
	time.sleep(0.1)
```

テストケースは以下のようになります。

```python:test_moge.py
from unittest import TestCase
from nose.tools import timed
from moge import do_something

class MogeTestCase(TestCase):
    @timed(0.2)
    def test_do_something(self):
        do_something()
```

## geventを使ったプログラムのテストを書く

※geventなんらじゃらほい、という方はスルーでお願いします！

geventを使ったプログラムでは、以下のようなデコレータを使うとよいです。

```python:gtestデコレータ
import gevent
def gtest(func):
	def _gtest(*args, **kwargs):
		gevent.spawn(func, *args, **kwargs).join()
	_gtest.__name__ = func.__name__
	return _gtest
```

以下のようなプログラムをテストしてみましょう。`_tick`と`_tacik`が交互に動き、tick1, tack1, tick2, tack2というような番号つきの文字列を交互にキューに送信していき、最後にキューから中身を全部吸い出して配列にして返してくれます。

```python:g.py
import gevent
import gevent.queue

def ticktack(n):
    q = gevent.queue.Queue()

    def _tick(_q, _n):
        for i in range(_n):
            _q.put('tick%d' % (i + 1))
            gevent.sleep()

    def _tack(_q, _n):
        for i in range(_n):
            _q.put('tack%d' % (i + 1))
            gevent.sleep()

    _tick_thread = gevent.spawn(_tick, q, n)
    _tack_thread = gevent.spawn(_tack, q, n)

    result = []
    while len(result) < n * 2:
        result.append(q.get())

    return result
```

gevent.sleep()が呼ばれたときにgeventが決定的なgreenlet切り替えを行うため、必ず先にspawnされた_tick、あとからspawnされた_tackが呼ばれます。

これを先のデコレータを使ってテストしてみましょう。

```python:test_g.py
from unittest import TestCase
from nose.tools import eq_
import gevent

from g import ticktack

import gevent
def gtest(func):
    def _gtest(*args, **kwargs):
        gevent.spawn(func, *args, **kwargs).join()
    _gtest.__name__ = func.__name__
    return _gtest

class GTestCase(TestCase):
    @gtest
    def test_ticktack(self):
        r1 = ticktack(1)
        eq_(r1, ['tick1', 'tack1'])
        r2 = ticktack(2)
        eq_(r2, ['tick1', 'tack1', 'tick2', 'tack2'])
```

いい感じになりましたでしょうか？

## まとめ

- noseは気軽かつ簡単にテストケースを書ける
- nosetestsはいろいろなオプションがあり実行も柔軟に行える
- geventを使ったプログラムのテストケースも工夫すればok

お役に立てばうれしいです。