テストを自動化したくてModipyd使ってみた

さっきインストールしたModipydを実際に使ってみた。
チュートリアルに従って動作させる。

Modipyd の自動テストツールを使ってみよう — Modipyd

テストコード用意

テスト用ディレクトリ「C:\pytest\」作成。
チュートリアルにあった2つのファイルを作成。

import os
import sys

class Widget(object):
    """
    Widget is a User Interface (UI) component object. A widget
    object claims a rectagular region of its content, is responsible
    for all drawing within that region.
    """

    def __init__(self, name, width=50, height=50):
        self.name = name
        self.width = width
        self.height = height

    def size(self):
        return (self.width, self.height)
import unittest
from widget import Widget

class SimpleWidgetTestCase(unittest.TestCase):

    def test_init(self):
        widget = Widget('The widget', 10, 10)
        self.failUnless(widget.size() == (10, 10),
                        'incorrect size')

    def test_default_size(self):
        widget = Widget('The widget')
        self.failUnless(widget.size() == (50, 50),
                        'incorrect default size')

if __name__ == '__main__':
    unittest.main()

ちゃんとある。

C:\pytest>dir
2010/03/01  19:23    <DIR>          .
2010/03/01  19:23    <DIR>          ..
2010/03/01  19:16               493 test_widget.py
2010/03/01  19:15               444 widget.py
テスト実行
C:\pytest>pyautotest -v
'pyautotest' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

あれーパス通ってないのかな。めんどいから直接動かす。

C:\pytest>C:\Python25\Lib\site-packages\Modipyd-1.0-py2.5.egg\modipyd\tools\autotest.py -v
[INFO] Loading plugin: <class 'modipyd.application.plugins.Autotest'>
[INFO] Loading BytecodeProcesser 'modipyd.bytecode.ImportProcessor'
[INFO] Loading BytecodeProcesser 'modipyd.bytecode.ClassDefinitionProcessor'
[INFO] Monitoring:
test_widget: C:\pytest\test_widget.py
  Dependencies: ['widget']
  Reverse: []
widget: C:\pytest\widget.py
  Dependencies: []
  Reverse: ['test_widget']

よし動いた。乱暴なやり方かもしれないけど動いた感動の方が勝ってる。
このままチュートリアルを進める。

やっとチュートリアル

上記 widget.py を少し変更する。

# 変更前
    def __init__(self, name, width=50, height=50):
        self.name = name
        self.width = width
        self.height = height

# 変更後
    def __init__(self, name, width=50, height=50):
        self.name = name
        self.width, self.height = width, height

すると、保存した途端にテストを実行して結果を吐き出してくれた。これは楽ですねー

[INFO] Reload module descriptor 'widget' at C:\pytest\widget.py
[INFO] Modified: widget: C:\pytest\widget.py
  Dependencies: []
  Reverse: ['test_widget']
[INFO] -> Affected: widget
[INFO] -> Affected: test_widget
[INFO] Running UnitTests: test_widget
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

ツールをまともに使った経験がないけど、テストで発生したエラーがとても分かりやすくて良いと思った。

テストでエラーを発生させ、修正する

テストを追加

import unittest
from widget import Widget

class SimpleWidgetTestCase(unittest.TestCase):

    def test_init(self):
        widget = Widget('The widget', 10, 10)
        self.failUnless(widget.size() == (10, 10),
                        'incorrect size')

    def test_default_size(self):
        widget = Widget('The widget')
        self.failUnless(widget.size() == (50, 50),
                        'incorrect default size')

class WidgetResizeTestCase(unittest.TestCase):

    def test_resize(self):
        widget = Widget('The widget', 15, 30)
        self.failUnless(widget.size() == (15, 30))
        widget.resize(45, 50)
        self.failUnless(widget.size() == (45, 50))

if __name__ == '__main__':
    unittest.main()

エラーが発生。

[INFO] Reload module descriptor 'widget' at C:\pytest\widget.py
[INFO] Modified: widget: C:\pytest\widget.py
  Dependencies: []
  Reverse: ['test_widget']
[INFO] -> Affected: widget
[INFO] -> Affected: test_widget
[INFO] Running UnitTests: test_widget
..E
======================================================================
ERROR: test_resize (test_widget.WidgetResizeTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_widget.py", line 21, in test_resize
    widget.resize(45, 50)
AttributeError: 'Widget' object has no attribute 'resize'

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (errors=1)

widget.py に resize() を実装。

import os
import sys

class Widget(object):
    """
    Widget is a User Interface (UI) component object. A widget
    object claims a rectagular region of its content, is responsible
    for all drawing within that region.
    """

    def __init__(self, name, width=50, height=50):
        self.name = name
        self.width, self.height = width, height

    def size(self):
        return (self.width, self.height)

    def resize(self):
        self.width, self.height = width, height

間違えてた。

[INFO] Reload module descriptor 'widget' at C:\pytest\widget.py
[INFO] Modified: widget: C:\pytest\widget.py
  Dependencies: []
  Reverse: ['test_widget']
[INFO] -> Affected: widget
[INFO] -> Affected: test_widget
[INFO] Running UnitTests: test_widget
..E
======================================================================
ERROR: test_resize (test_widget.WidgetResizeTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_widget.py", line 21, in test_resize
    widget.resize(45, 50)
TypeError: resize() takes exactly 1 argument (3 given)

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (errors=1)

今度こそ修正。

import os
import sys

class Widget(object):
    """
    Widget is a User Interface (UI) component object. A widget
    object claims a rectagular region of its content, is responsible
    for all drawing within that region.
    """

    def __init__(self, name, width=50, height=50):
        self.name = name
        self.width, self.height = width, height

    def size(self):
        return (self.width, self.height)

    def resize(self, width, height):
        self.width, self.height = width, height

テストに通った。

[INFO] Reload module descriptor 'widget' at C:\pytest\widget.py
[INFO] Modified: widget: C:\pytest\widget.py
  Dependencies: []
  Reverse: ['test_widget']
[INFO] -> Affected: widget
[INFO] -> Affected: test_widget
[INFO] Running UnitTests: test_widget
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

自動的にテストしてくれるというのはとっても楽なことなんだと思った。
あと自身がない箇所や、自分が安心したい箇所をテストにしてクリアしていくというのはとても脳汁溢れる良い行為だと思いました。