web-dev-qa-db-ja.com

ディレクトリですべてのPython単体テストを実行するにはどうすればよいですか?

Python単体テストを含むディレクトリがあります。各ユニットテストモジュールの形式は、test _ *。pyです。 all_test.pyというファイルを作成しようとしていますが、それは、あなたが推測したように、前述のテストフォームのすべてのファイルを実行し、結果を返します。これまでに2つの方法を試しました。両方とも失敗しました。 2つの方法を示しますが、実際にこれを正しく行う方法を知っている人がいることを願っています。

私の最初の勇敢な試みとして、「ファイル内のすべてのテストモジュールをインポートし、それからこのunittest.main() doodadを呼び出すと、動作するでしょうか?」と考えました。まあ、私は間違っていたことがわかります。

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

if __== "__main__":
     unittest.main()

これは機能しませんでした、私が得た結果は:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

しかし、2回目の試行では、このテスト全体をより「手動」の方法で実行しようとするかもしれません。だから私はそれを以下にしようとしました:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __== "__main__":
    unittest.main()

これも機能しませんでしたが、とても近いようです!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

ある種のスイートがあるようで、結果を実行できます。私はrun=1しか持っていないということを少し心配していますが、それはrun=2のように思えますが、進行中です。しかし、どのように結果をメインに渡し、表示するのですか?または、このファイルを実行するために基本的にどのように動作させるのですか?そうすることで、このディレクトリ内のすべての単体テストを実行しますか?

242
Stephen Cagle

これを行うテストランナーを使用できます。 nose は非常に良い例です。実行すると、現在のツリーでテストを見つけて実行します。

更新しました:

ここに私の鼻の前の日からのいくつかのコードがあります。おそらく、モジュール名の明示的なリストは必要ないでしょうが、おそらく残りは役に立つでしょう。

testmodules = [
    'cogapp.test_makefiles',
    'cogapp.test_whiteutils',
    'cogapp.test_cogapp',
    ]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        # If the module defines a suite() function, call it to get the suite.
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        # else, just load all the test cases from the module.
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)
98
Ned Batchelder

Python 2.7以降では、新しいコードを記述したり、サードパーティのツールを使用してこれを行う必要はありません。コマンドラインを介した再帰的なテストの実行が組み込まれています。

python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'

python 2.7 または python 3.x unittestのドキュメントで詳細を読むことができます。

381
Travis Bear

これは、unittestから直接可能になりました: nittest.TestLoader.discover

import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)

runner = unittest.TextTestRunner()
runner.run(suite)
50
slaughter98

python 3では、unittest.TestCaseを使用している場合:

  • testディレクトリに空の(またはそれ以外の)__init__.pyファイルが必要です(must be named named test/
  • test/内のテストファイルは、パターンtest_*.pyと一致します。これらはtest/の下のサブディレクトリ内に配置でき、これらのサブディレクトリには任意の名前を付けることができます。

次に、以下を使用してすべてのテストを実行できます。

python -m unittest

できた! 100行未満のソリューション。別のpython初心者がこれを見つけることで時間を節約できれば幸いです。

41
tmck-code

さて、上記のコードを少し調べて(具体的にはTextTestRunnerdefaultTestLoaderを使用して)、かなり接近することができました。最終的には、「手動」で追加するのではなく、すべてのテストスイートを単一のスイートコンストラクターに渡すことでコードを修正し、他の問題を修正しました。だからここに私の解決策があります。

import glob
import unittest

test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)

ええ、おそらくこれをするよりも鼻を使う方が簡単かもしれませんが、それはポイントのほかです。

30
Stephen Cagle

さまざまなテストケースクラスからすべてのテストを実行したい場合、それらを明示的に指定したい場合は、次のようにできます。

from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns

if __== "__main__":

    loader = TestLoader()
    tests = [
        loader.loadTestsFromTestCase(test)
        for test in (TestSymbols, TestPatterns)
    ]
    suite = TestSuite(tests)

    runner = TextTestRunner(verbosity=2)
    runner.run(suite)

ここで、uclidは私のプロジェクトであり、TestSymbolsおよびTestPatternsTestCaseのサブクラスです。

23

discoverメソッドとload_testsのオーバーロードを使用して、コードの(最小限の、私は)行数でこの結果を達成しました。

def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
    suite = TestSuite()
    for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
        for test_suite in all_test_suite:
            suite.addTests(test_suite)
    return suite

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

5のようなもののようなもの

Ran 27 tests in 0.187s
OK
13
rds

さまざまなアプローチを試しましたが、すべてに欠陥があるように見えるか、いくつかのコードを作成する必要があり、それは面倒です。しかし、Linuxには便利な方法があります。特定のパターンですべてのテストを見つけて、それらを1つずつ呼び出します。

find . -name 'Test*py' -exec python '{}' \;

そして最も重要なことは、それは間違いなく動作します。

7
zinking

packagedライブラリまたはアプリケーションの場合、それを行いたくありません。 setuptoolsあなたのためにそれを行います

このコマンドを使用するには、関数、TestCaseクラスまたはメソッド、またはunittestクラスを含むモジュールまたはパッケージのいずれかによって、プロジェクトのテストをTestCaseテストスイートにラップする必要があります。名前付きスイートがモジュールであり、モジュールにadditional_tests()関数がある場合、そのモジュールが呼び出され、実行されるテストに結果(unittest.TestSuiteでなければなりません)が追加されます。名前付きスイートがパッケージの場合、すべてのサブモジュールとサブパッケージがテストスイート全体に再帰的に追加されます

次のように、ルートテストパッケージの場所を指定するだけです。

setup(
    # ...
    test_suite = 'somepkg.test'
)

そして、python setup.py testを実行します。

discover はファイルインポートを使用するため、テストスイートでの相対的なインポートを回避しない限り、Python 3ではファイルベースの検出に問題がある可能性があります。オプションのtop_level_dirをサポートしていますが、無限の再帰エラーがいくつかありました。したがって、パッケージ化されていないコードの簡単な解決策は、テストパッケージの__init__.pyに以下を追加することです( load_tests Protocol を参照)。

import unittest

from . import foo, bar


def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    suite.addTests(loader.loadTestsFromModule(foo))
    suite.addTests(loader.loadTestsFromModule(bar))

    return suite
6
saaj

私はPyDev/LiClipseを使用していますが、GUIからすべてのテストを一度に実行する方法を実際に理解していません。 (編集:ルートテストフォルダーを右クリックし、Run as -> Python unit-testを選択します

これは私の現在の回避策です:

import unittest

def load_tests(loader, tests, pattern):
    return loader.discover('.')

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

このコードをテストディレクトリのallというモジュールに配置しました。このモジュールをLiClipseから単体テストとして実行すると、すべてのテストが実行されます。特定のテストまたは失敗したテストのみを繰り返すように要求した場合、それらのテストのみが実行されます。コマンドラインのテストランナーにも干渉しません(nosetests)-無視されます。

プロジェクトのセットアップに基づいて、引数をdiscoverに変更する必要がある場合があります。

4
Dunes

Stephen Cagle の回答に基づいて、ネストされたテストモジュールのサポートを追加しました。

import fnmatch
import os
import unittest

def all_test_modules(root_dir, pattern):
    test_file_names = all_files_in(root_dir, pattern)
    return [path_to_module(str) for str in test_file_names]

def all_files_in(root_dir, pattern):
    matches = []

    for root, dirnames, filenames in os.walk(root_dir):
        for filename in fnmatch.filter(filenames, pattern):
            matches.append(os.path.join(root, filename))

    return matches

def path_to_module(py_file):
    return strip_leading_dots( \
        replace_slash_by_dot(  \
            strip_extension(py_file)))

def strip_extension(py_file):
    return py_file[0:len(py_file) - len('.py')]

def replace_slash_by_dot(str):
    return str.replace('\\', '.').replace('/', '.')

def strip_leading_dots(str):
    while str.startswith('.'):
       str = str[1:len(str)]
    return str

module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
    in module_names]

testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)

このコードは、.のすべてのサブディレクトリで*Tests.pyファイルを検索し、これらのファイルが読み込まれます。それは、各*Tests.pyが単一のクラス*Tests(unittest.TestCase)を含むことを期待しています。このクラスは順番にロードされ、次々に実行されます。

これは、ディレクトリ/モジュールの任意の深いネストで機能しますが、その間の各ディレクトリには少なくとも空の__init__.pyファイルが含まれている必要があります。これにより、テストは、スラッシュ(またはバックスラッシュ)をドット(replace_slash_by_dotを参照)に置き換えることで、ネストされたモジュールをロードできます。

2
Peter Kofler

このBASHスクリプトは、現在の作業ディレクトリに関係なく、ファイルシステムのANYWHEREからpython unittestテストディレクトリを実行します。その作業ディレクトリは、常にtestディレクトリが置かれている場所にあります。

すべてのテスト、独立した$ PWD

unittest Pythonモジュールは、(discover -sオプションを使用して)場所を指定しない限り、現在のディレクトリに依存します。

これは、./srcまたは./example作業ディレクトリにとどまり、素早い全体的な単体テストが必要な場合に便利です。

#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v

選択されたテスト、独立した$ PWD

このユーティリティファイルにrunone.pyという名前を付け、次のように使用します。

runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest $1)

実動中にパッケージ/メモリのオーバーヘッドに負担をかけるtest/__init__.pyファイルは必要ありません。

1
Egbert S

テスト発見は完全な主題であると思われるため、テスト発見のためのいくつかの専用フレームワークがあります:

詳細はこちら: https://wiki.python.org/moin/PythonTestingToolsTaxonomy

0
Bactisme