Python用の単体テストフレームワークである Pikzie 0.9.2がリリースされま した。
以下のようにeasy_installでインストールできます。
% sudo easy_install Pikzie
Python用の単体テストフレームワークとしてはPythonに標準添付さ れている unittest や、unittestよりも柔軟にテストが書ける py.testなど があります。
また、unittest自体を拡張してより柔軟にテストが書けるようにし た nose もあります。noseはプラグイン方式をサポートしており、柔軟にテ ストが書ける機能以外にも、プラグインとして以下のような機能を 提供しています。
また、BDD用のテスティングフレームワークとしては pyspecがあります。
上記に挙げた既存の単体テストフレームワークには共通して以下の ような問題点があります*1。
上記の問題を解決することがPikzieを使うもっとも大きな理由にな ります。これは、テストの失敗結果を使いながらデバッグすること が多いからです。
テストを書くことの重要性、テストの書き方*2などを解説しているものはよくみますが、 テストが失敗してそれを修正していく過程を書いているものはなか なかみかけません。しかし、テストは一度成功したらそれ以降も成 功し続けるわけではないのです。開発の途中でテストは何度も何度 も失敗します。例えば、以下のような場合に既存のテストが失敗す るかもしれません。
つまり、新しいテスト・機能を開発していくときに既存のテストが 失敗することは当たり前のことです。
以下はunittestで書かれたテストです。
# unittest-test.py import unittest class FailTestCase(unittest.TestCase): def test_fail(self): x = 1110111011110 self.assertEquals(x + 100000, 1111111011111) if __name__ == '__main__': unittest.main()
このテストの実行結果は以下のようになります。
% python unittest-test.py
F
======================================================================
FAIL: test_fail (__main__.FailTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "unittest-test.py", line 6, in test_fail
self.assertEquals(x + 100000, 1111111011111)
AssertionError: 1110111111110 != 1111111011111
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
期待値と実測値は以下のように表示されています。
AssertionError: 1110111111110 != 1111111011111
どこが違うのかがわかりにくいのがわかると思います。
noseを用いた場合もテストの書き方は変わりません。ただし、テス トを実行するためにnosetestsコマンドを使う必要があります。
% nosetests unittest-test.py
F
======================================================================
FAIL: test_fail (unittest-test.FailTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/unittest-test.py", line 6, in test_fail
self.assertEquals(x + 100000, 1111111011111)
AssertionError: 1110111111110 != 1111111011111
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
以下はpy.testで書いたテストです。
# pytest-test.py import py def test_fail(): x = 1110111011110 assert x + 100000 == 1111111011111
このテストの実行結果は以下のようになります。py.testコマンド で起動します。
% /tmp/py-0.9.1/py/bin/py.test pytest-test.py
inserting into sys.path: /tmp/py-0.9.1
============================= test process starts =============================
executable: /usr/bin/python (2.5.2-final-0)
using py lib: /tmp/py-0.9.1/py <rev unknown>
pytest-test.py[1] F
_______________________________________________________________________________
____________________________ entrypoint: test_fail ____________________________
def test_fail():
x = 1110111011110
E assert x + 100000 == 1111111011111
> assert (1110111011110 + 100000) == 1111111011111
[/tmp/pytest-test.py:5]
_______________________________________________________________________________
================== tests finished: 1 failed in 0.08 seconds ===================
unittestと違ってxの値が展開されていますが、
(1110111011110 + 100000)の結果は表示されていません。こ
のため、実際の値が期待値とどう違うのかがわかりません。
最後にPikzieで書いたテストです。
# pikzie-test.py import pikzie class TestFail(pikzie.TestCase): def test_fail(self): x = 1110111011110 self.assert_equal(1111111011111, x + 100000)
実行結果は以下の通りです。特別なテスト起動コマンドは必要あり ません。
% python pikzie-test.py F 1) Failure: TestFail.test_fail: self.assert_equal(1111111011111, x + 100000) pikzie-test.py:6: self.assert_equal(1111111011111, x + 100000) expected: <1111111011111> but was: <1110111111110> Finished in 0.005 seconds 1 test(s), 0 assertion(s), 1 failure(s), 0 error(s), 0 pending(s), 0 notification(s)
以下のように期待値と実測値が並べて表示されるので、違いをみつ けやすくなります。
expected: <1111111011111> but was: <1110111111110>
また、場合によってはdiffが表示されます。
# pikzie-test-diff.py import pikzie class TestDiff(pikzie.TestCase): def test_diff(self): self.assert_equal("aaaaaxaaaaaaaaa", "aaaaaoaaaaaaaaa")
実行結果です。
% python pikzie-test-diff.py
F
1) Failure: TestDiff.test_diff: self.assert_equal("aaaaaxaaaaaaaaa", "aaaaaoaaaaaaaaa")
pikzie-test-diff.py:5: self.assert_equal("aaaaaxaaaaaaaaa", "aaaaaoaaaaaaaaa")
expected: <'aaaaaxaaaaaaaaa'>
but was: <'aaaaaoaaaaaaaaa'>
diff:
- aaaaaxaaaaaaaaa
? ^
+ aaaaaoaaaaaaaaa
? ^
Finished in 0.033 seconds
1 test(s), 0 assertion(s), 1 failure(s), 0 error(s), 0 pending(s), 0 notification(s)
また、長い行の場合は折り返してdiffを表示します。
# pikzie-test-diff-long import pikzie class TestDiffLong(pikzie.TestCase): def test_diff_long(self): self.assert_equal("ppppppppppppppppppppppyyyyyyyyyyyyyyy" "ttttttttttttttttttttttttttttttttttttt" "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhon", "ppppppppppppppppppppppyyyyyyyyyyyyyyy" "ttttttttttttttttttttttttttttttttttttt" "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhoon")
実行結果です。
% python pikzie-test-diff-long.py F 1) Failure: TestDiffLong.test_diff_long: "ppppppppppppppppppppppyyyyyyyyyyyyyyy" pikzie-test-diff-long.py:8: "ppppppppppppppppppppppyyyyyyyyyyyyyyy" expected: <'ppppppppppppppppppppppyyyyyyyyyyyyyyyttttttttttttttttttttttttttttttttttttthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhon'> but was: <'ppppppppppppppppppppppyyyyyyyyyyyyyyyttttttttttttttttttttttttttttttttttttthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhoon'> diff: - ppppppppppppppppppppppyyyyyyyyyyyyyyyttttttttttttttttttttttttttttttttttttthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhon ? ^ + ppppppppppppppppppppppyyyyyyyyyyyyyyyttttttttttttttttttttttttttttttttttttthhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhoon ? ^ folded diff: ppppppppppppppppppppppyyyyyyyyyyyyyyyttttttttttttttttttttttttttttttttttttthhhh - hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhon ? - + hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhoon ? + Finished in 0.008 seconds 1 test(s), 0 assertion(s), 1 failure(s), 0 error(s), 0 pending(s), 0 notification(s)
diffの後に、長い行を折り返した結果に対するdiffも表示されてい ます。期待値・実測値が長い文字列で表現される場合は、折り返し た結果のdiffを見た方が異なる部分を見つけやすくなります。
テストは頻繁に失敗します。Pikzieはテストの修正に必要な情報を できるだけ多く、簡潔に表示します。これは、テストの修正を迅速 に行うために大事なことです。
ここでは書きませんでしたが、もちろん、Pikzieは他のテスティン グフレームワークと同じように柔軟にテストを書くことができます。
*1 unittestは命名規則がCamelCaseで PEP 8 -- Style Guide for Python Codeから外れ ているという問題もあります。
*2 例えば、テストの粒 度やテスト駆動開発など