ククログ

株式会社クリアコード > ククログ > Pikzie 0.9.2リリース

Pikzie 0.9.2リリース

Python用の単体テストフレームワークである Pikzie 0.9.2がリリースされま した。

以下のようにeasy_installでインストールできます。

% sudo easy_install Pikzie

類似のテスティングフレームワーク

Python用の単体テストフレームワークとしてはPythonに標準添付さ れている unittest や、unittestよりも柔軟にテストが書ける py.testなど があります。

また、unittest自体を拡張してより柔軟にテストが書けるようにし た nose もあります。noseはプラグイン方式をサポートしており、柔軟にテ ストが書ける機能以外にも、プラグインとして以下のような機能を 提供しています。

  • テストを省略するskipの追加
  • カバレッジをレポートする機能
  • doctest のサポート
  • ...

また、BDD用のテスティングフレームワークとしては pyspecがあります。

Pikzieを使う理由

上記に挙げた既存の単体テストフレームワークには共通して以下の ような問題点があります1

  • 期待値(expected)と実測値(actual)の違いを判断することが困 難な出力結果

上記の問題を解決することが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. 例えば、テストの粒 度やテスト駆動開発など