web-dev-qa-db-ja.com

再帰的なユニットテスト発見

ユニットテストを格納するディレクトリ「tests」のパッケージがあります。私のパッケージは次のようになります:

.
├── LICENSE
├── models
│   └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│   ├── db
│   │   └── test_employee.py
│   └── test_tc.py
└── todo.txt

私のパッケージディレクトリから、tests/test_tc.pytests/db/test_employee.pyの両方を検索できるようにしたいと考えています。サードパーティのライブラリ(noseなど)をインストールしたり、TestSuiteを手動でビルドしてこれを実行したりする必要はありません。

確かに、テストが見つかったときにunittest discoverが停止しないように指示する方法はありますか? python -m unittest discover -s teststests/test_tc.pyを検索し、python -m unittest discover -s tests/dbtests/db/test_employee.pyを検索します。両方を見つける方法はありませんか?

33
Adam Smith

少し掘り下げると、より深いモジュールがインポート可能である限り、それらはpython -m unittest discoverを介して発見されるようです。その場合の解決策は、__init__.pyファイルを各ディレクトリに追加してパッケージにすることでした。

.
├── LICENSE
├── models
│   └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│   ├── db
│   │   ├── __init__.py       # NEW
│   │   └── test_employee.py
│   ├── __init__.py           # NEW
│   └── test_tc.py
└── todo.txt

各ディレクトリに__init__.pyがある限り、python -m unittest discoverは関連するtest_*モジュールをインポートできます。

59
Adam Smith

テスト内に___init__.py_ファイルを追加しても問題ない場合は、検出を処理する_load_tests_関数をそこに配置できます。

テストパッケージ名(___init__.py_を含むディレクトリ)がパターンに一致する場合、パッケージは 'load_tests'関数についてチェックされます。これが存在する場合、ローダー、テスト、パターンで呼び出されます。

Load_testsが存在する場合、検出はパッケージにしない再帰します。load_testsは、パッケージ内のすべてのテストをロードします。

これが最良の方法であるとは思いませんが、その関数を記述する1つの方法は次のとおりです。

_import os
import pkgutil
import inspect
import unittest

# Add *all* subdirectories to this module's path
__path__ = [x[0] for x in os.walk(os.path.dirname(__file__))]

def load_tests(loader, suite, pattern):
    for imp, modname, _ in pkgutil.walk_packages(__path__):
        mod = imp.find_module(modname).load_module(modname)
        for memname, memobj in inspect.getmembers(mod):
            if inspect.isclass(memobj):
                if issubclass(memobj, unittest.TestCase):
                    print("Found TestCase: {}".format(memobj))
                    for test in loader.loadTestsFromTestCase(memobj):
                        print("  Found Test: {}".format(test))
                        suite.addTest(test)

    print("=" * 70)
    return suite
_

かなり醜いです。

最初に、すべてのサブディレクトリをテストパッケージのパスに追加します( Docs )。

次に、 pkgutil を使用してパスをたどり、パッケージまたはモジュールを探します。

見つかった場合は、モジュールメンバーをチェックして、それらがクラスであるかどうか、クラスである場合は_unittest.TestCase_のサブクラスであるかどうかを確認します。そうであれば、クラス内のテストはテストスイートにロードされます。

したがって、プロジェクトルート内から次のように入力できます。

_python -m unittest discover -p tests
_

_-p_パターンスイッチを使用する。すべてがうまくいけば、私が見たものを見ることができます。それは次のようなものです。

_Found TestCase: <class 'test_tc.TestCase'>
  Found Test: testBar (test_tc.TestCase)
  Found Test: testFoo (test_tc.TestCase)
Found TestCase: <class 'test_employee.TestCase'>
  Found Test: testBar (test_employee.TestCase)
  Found Test: testFoo (test_employee.TestCase)
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK
_

これは予想どおりでした。私の2つのサンプルファイルには、それぞれtestFootestBarの2つのテストが含まれていました。

編集:さらに掘り下げた後、この関数を次のように指定できるようです:

_def load_tests(loader, suite, pattern):
    for imp, modname, _ in pkgutil.walk_packages(__path__):
        mod = imp.find_module(modname).load_module(modname)
        for test in loader.loadTestsFromModule(mod):
            print("Found Tests: {}".format(test._tests))
            suite.addTests(test)
_

これは、上記で使用したloader.loadTestsFromModule()メソッドの代わりにloader.loadTestsFromTestCase()メソッドを使用します。それでもtestsパッケージパスを変更し、モジュールを探して歩きます。これがここでの鍵だと思います。

見つかったテストスイートを一度にメインのテストスイートsuiteに追加しているため、出力は少し異なります。

_python -m unittest discover -p tests
Found Tests: [<test_tc.TestCase testMethod=testBar>, <test_tc.TestCase testMethod=testFoo>]
Found Tests: [<test_employee.TestCase testMethod=testBar>, <test_employee.TestCase testMethod=testFoo>]
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK
_

ただし、両方のクラス、両方のサブディレクトリで、期待した4つのテストを引き続き取得します。

6
jedwards