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

このチュートリアルを実際に試すには、なにはともあれ Modipyd をインストールしなくてはいけません。インストールの手順についてはModipyd のインストールが参考になるはずです。

簡単な例:Widget クラス

これから、ふたつの簡単な Python スクリプトを例に、Modipyd の自動テストツールの使い方を説明していきます。:

[~/modipyd/examples/widget]
% ls
test_widget.py        widget.py

widget.pyWidget クラスを含む Python スクリプトです。一方、test_widget.py はテストケースになります。

widget.py:

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)
        

test_widget.py:

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()

pyautotest の実行

pyautotest を実行すると、これらの Python ファイルの更新を監視するようになります(-v/--verbose オプションを指定すると、プログラムの処理経過が分かるようになるので便利です):

% pyautotest -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: test_widget.py
  Dependencies: ['widget']
  Reverse: []
widget: widget.py
  Dependencies: []
  Reverse: ['test_widget']

最後の出力が示しているように、pyautotestwidget.pytest_widget.py の監視を開始しています。

リファクタリング:初期化での代入をひとつにする

ここで widget.py を見てみましょう。Widget.__init__() では 3 つある引数をそれぞれ、インスタンス変数に代入していますね。Python ではタプルを使うことで、複数の代入文をひとつにまとめることができます。それでは、widget.py をそのように編集します。

widget.py:

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)
        

widget.py を編集すると、pyautotest は自動的に、変更されたファイル(個の場合 widget.py)を読み込み、関連するテストケースをすべて実行します(この場合 test_widget.py)。

Note

関連するテストケースを特定するとき、pyautotest はファイル名のパターンを使用しません。 そのため、テストケースのファイル名は widget_test.pyWidgetTest.pytest/widget.py など、自由につけることができます。

[INFO] Reload module descriptor 'widget' at widget.py
[INFO] Modified: widget: widget.py
  Dependencies: []
  Reverse: ['test_widget']
[INFO] -> Affected: widget
[INFO] -> Affected: test_widget
[INFO] Running UnitTests: test_widget
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

resize() メソッドの追加

Widget クラスには足りない機能がたくさんあります。この節では resize() メソッドを追加しましょう。:func:resize メソッドはふたつの引数 widthheight をとり、自身の大きさを変更します。テスト駆動開発の精神にのっとり、Widget.resize() を実装するまえにテストケースを書いていきます。

test_widget.py:

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()

新しいテストケースとして WidgetResizeTestCase を追加しました。まだ Widget.resize() がないので、このテストは失敗するはずです。

Note

pyautotest は新しく追加された unittest.TestCase のサブクラスを 自動的に検出します。

======================================================================
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

予想通り、テストは失敗しました。それでは、Widget.resize() を実装しましょう。

widget.py:

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.resize(width, height)

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

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

この変更を保存すると、pyautotest が自動でテストケースを実行し、そのテストは成功します。

[INFO] Running UnitTests: test_widget
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK