Python
nose

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

More than 3 years have passed since last update.

pythonのテストフレームワークはいろいろありますが、個人的にはnoseというフレームワークがシンプルで好きですので、紹介したいと思います。


noseのインストール

$ pip install nose

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


noseテストケースを書く

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


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文を入れてみます。


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のページを参照してください。


raises

raisesは例外が起きそうなテストに対してデコレータとして使います。

例外が起きそうな以下の関数divをテストします。


fuga.py

def div(n, m):

return n / m

テストケースは以下。


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秒以内で終わることを検査するテストを考えます。


moge.py

import time

def do_something():
time.sleep(0.1)

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


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を使ったプログラムでは、以下のようなデコレータを使うとよいです。


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というような番号つきの文字列を交互にキューに送信していき、最後にキューから中身を全部吸い出して配列にして返してくれます。


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が呼ばれます。

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


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

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