unittest 単体テスト入門 その2 テストパッケージとテストスイート

前回はunittestモジュールの基本的な使い方について学習しました。今回はその続きで、テストパッケージの配置と実行方法、グループ化について解説します。

プロジェクト構成とテストの実行

Pythonに限らずプロジェクトの構成として、アプリケーションのソースコードとテストのソースコードは別パッケージとしてディレクトリを分けることが一般的です。以下のような構成の場合の実行方法について考えてみましょう。

.                        # プロジェクトディレクトリ(ここで実行する)
├── sample_lib        # アプリケーションのパッケージ
│   ├── __init__.py
│   ├── mod1.py
│   └── mod2.py
└── test              # テストのパッケージ
    ├── __init__.py
    ├── test_mod1.py
    └── test_mod2.py

mod1.py、mod2.pyというコードに対するテストコードが用意されているものとします。これらのテストを実行する際、プロジェクトディレクトリ直下で以下のように-mオプションでunittestモジュールを実行し、相対パスでテストスクリプトを指定します。

$ python -m unittest test/test_mod1.py 
test_add_num
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

$ python -m unittest test/test_mod2.py 
test_is_positive
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

それぞれのテストスクリプトにif __name__ == '__main__':をつけて実行する方法もあるのですが、その場合はエントリーポイントがtestディレクトリ配下になってしまいますので、予めPYTHON_PATHをエクスポートするなどの措置が必要になります。

テストスイート(テストのグループ化)

上のプロジェクトの場合テストスクリプトがたった2つなので順番にpythonコマンドを実行しても良いのですが、プロジェクトによってはこれが数十、数百になることも考えられます。こういった時のためPythonはunittestをグループ化してまとめて実行することが可能です。(一般的にテストスイートと呼ばれています。)

テストスイートの作成方法

さっそくテストスイートを作成してみましょう。runner.pyという名前でテストクラスを作成します。

.
├── sample_lib
│   ├── __init__.py
│   ├── mod1.py
│   └── mod2.py
└── test
    ├── __init__.py
    ├── runner.py        # テストスイートクラス
    ├── test_mod1.py
    └── test_mod2.py

runner.pyは以下のように書きます。

# runner.py
import unittest
import test.test_mod1
import test.test_mod2

class TestRunner(unittest.TestCase):

    def test_runner(self):
        test_suite = unittest.TestSuite()
        test_suite.addTest(unittest.makeSuite(test.test_mod1.TestMod1))
        test_suite.addTest(unittest.makeSuite(test.test_mod2.TestMod2))
        unittest.TextTestRunner().run(test_suite)

unittest.TestSuite()オブジェクトを生成し、addTestでテストクラスを指定します。TextTestRunner().run()が呼び出されるとaddしたテストクラスが実行されます。

今までと同様に-mオプションをつけ、テストスイートクラスを指定すると、addしたテストケースが実行されていることが確認できます。

$ python -m unittest test/runner.py 
.
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

discover(テストスイート作成の自動化)

上のテストスイートではaddTestで1つ1つ追加していきましたが、これもモジュール数が多いと管理が大変そうですね。このため、ディレクトリ単位でテストモジュールを探索し、自動でテストスイートを作成してくれる仕組みがあります。これをdiscoverと呼びます。

実はテストモジュールを指定せずに以下のように打ち込むと、プロジェクト全体を走査してテストを見つけ実行してくれます。

python -m unittest

ですが、サブプロジェクト単位でテストスイートを実行したい場合などは、上で作成したrunnerに自分でdiscoveryを記述してカスタマイズすることが可能です。上のrunner.pyをdiscoverを使用したものに修正してみましょう。

# runner.py
import unittest
import test.test_mod1
import test.test_mod2

class TestRunner(unittest.TestCase):

    def test_runner(self):
        test_suite = unittest.TestSuite()

        # testクラスを見つけ出す
        tests = unittest.defaultTestLoader.discover("test", pattern="test_*.py")

        # 見つけたtestクラスを追加する
        test_suite.addTest(tests)
        unittest.TextTestRunner().run(test_suite)

discoverの引数にはディレクトリを指定します。ローカルでテストを実行する場合は、機能単位でテストをする等をして時間を節約することが可能です。