pythonのテストフレームワークはいろいろありますが、個人的にはnoseというフレームワークがシンプルで好きですので、紹介したいと思います。
noseのインストール
$ pip install nose
環境に応じてsudoが必要かもしれないですね。
noseテストケースを書く
hogeというモジュールに以下のような関数sum, および関数is_evenがあると想定します。
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文を入れてみます。
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
をテストします。
def div(n, m):
return n / m
テストケースは以下。
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秒以内で終わることを検査するテストを考えます。
import time
def do_something():
time.sleep(0.1)
テストケースは以下のようになります。
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を使ったプログラムでは、以下のようなデコレータを使うとよいです。
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というような番号つきの文字列を交互にキューに送信していき、最後にキューから中身を全部吸い出して配列にして返してくれます。
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が呼ばれます。
これを先のデコレータを使ってテストしてみましょう。
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
お役に立てばうれしいです。