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


315

私の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 __name__ == "__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 __name__ == "__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、進捗です。しかし、結果をmainに渡し、表示するにはどうすればよいですか?または、このファイルを実行できるように基本的にはどのように機能させるのですか?そうすることで、このディレクトリですべての単体テストを実行できますか?


1
Python 2.7以降を使用している場合はTravisの回答にスキップしてください
Rocky

テストインスタンスオブジェクトからテストを実行しようとしたことがありますか?
ピノキオ

ファイル構造の例を使用した解決策については、この回答を参照してください。
Derek Soike 2017年

回答:


477

Python 2.7以降では、これを行うために新しいコードを記述したり、サードパーティのツールを使用したりする必要はありません。コマンドラインによる再帰テストの実行が組み込まれています。入れて__init__.pyテストディレクトリにと:

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

詳細については、python 2.7 またはpython 3.x unittestのドキュメントをご覧ください。


11
問題は次のとおりはImportError:スタートディレクトリがインポート可能ではありません:
zinking

6
少なくともLinux上のPython 2.7.8では、コマンドラインからの呼び出しでも再帰はありません。私のプロジェクトには、ユニットテストがそれぞれの「unit_tests / <subproject> / python /」ディレクトリにあるいくつかのサブプロジェクトがあります。そのようなパスを指定すると、そのサブプロジェクトのユニットテストが実行されますが、テストディレクトリ引数として「unit_tests」だけを指定しても、テストは見つかりませんでした(すべてのサブプロジェクトのすべてのテストではなく)。ヒントはありますか?
user686249

6
再帰について:<test_directory>のない最初のコマンドはデフォルトで "。"になります。そして、サブモジュールに再帰します。つまり、検出するすべてのテストディレクトリには、init .py が必要です。存在する場合は、discoverコマンドで検出されます。試してみただけでうまくいきました。
EmilStenström2016年

これでうまくいきました。私は4つのファイルを含むテストフォルダーを持っています。これをLinuxターミナルから実行します。
JasTonAChair 2016

5
ありがとう!なぜこれは受け入れられた答えではないのですか?私の見解では、より良い答えは、常に外部の依存関係を必要としないものです...
Jonathan Benn

108

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

更新しました:

これが私のプレノーズ時代のコードです。おそらくモジュール名の明示的なリストは必要ありませんが、残りは役に立つかもしれません。

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)

2
この方法の利点は、すべてのテストモジュールを1つのtest_all.pyモジュールに明示的にインポートし、Unittest.main()を呼び出すだけで、一部のモジュールではテストスイートを宣言し、他のモジュールではテストスイートを宣言できないということですか?
Corey Porter

1
私は鼻を試してみましたが、それは完璧に機能します。プロジェクトでのインストールと実行は簡単でした。virtualenv内で実行する数行のスクリプトで自動化することもできました。鼻に+1!
Jesse Webb

常に実行できるとは限りません。プロジェクトの構造をインポートすると、モジュールでインポートを実行しようとすると、鼻が混乱することがあります。
chiffa

4
注意鼻が「過去数年間のメンテナンスモードで」されており、それが現在使用することをお勧めしnose2pytest、あるいは単なる単体テスト / unittest2新しいプロジェクトのために。
Kurt Peek 2017年

テストインスタンスオブジェクトからテストを実行しようとしたことがありますか?
ピノキオ

96

Python 3では、使用している場合unittest.TestCase

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

その後、次のコマンドですべてのテストを実行できます。

python -m unittest

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


3
デフォルトでは、「test」で始まるファイル名のテストのみを検索することに注意してください
Shawabawa

3
正解です。元の質問は「各ユニットテストモジュールの形式がtest _ *。py。」であるという事実を参照していたため、この回答は直接返信されます。私は今、答えはより明確であることを更新しました
TMCKコード

1
おかげで、トラビスベアの回答を使用するのに欠けていたことがわかりました。
Jeremy Cochoy、

65

これはunittestから直接実行できるようになりました:unittest.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)

3
私はこの方法も試しましたが、いくつかのテストがありますが、完全に動作します。優れた!!!しかし、私は4つのテストしかないことに興味があります。一緒にそれらは0.032sを実行しますが、この方法を使用してすべてを実行すると、結果が得られ.... ---------------------------------------------------------------------- Ran 4 tests in 0.000s OKます 違いはどこから来たのですか?
simkus

コマンドラインから次のようなファイルを実行できません。どのように呼び出す必要がありますか?
ダスティン

python file.py
虐殺

1
完璧に働いた!test / dirに設定してから、start_id = "./"を設定してください。私見、この答えは現在(Python 3.7)受け入れられています!
jjwdesign

最後の行を´res = runner.run(suite);に変更できます。sys.exit(0 if res.wasSuccessful()else 1) ´正しい終了コードが必要な場合
Sadap

32

まあ(特に使用してビット上記のコードを研究することによってTextTestRunner、およびdefaultTestLoader)、私はかなり近づくことができました。最終的に、すべてのテストスイートを「手動で」追加するのではなく、単一のスイートコンストラクターに渡すだけでコードを修正し、他の問題を修正しました。これが私の解決策です。

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)

ええ、おそらくこれを行うよりも鼻を使う方が簡単ですが、それは重要なことです。


良い、それは現在のディレクトリでうまく機能します、どのようにサブダイレクトを呼び出すのですか?
Larry Cai

ラリー、再帰テストの発見については新しい回答(stackoverflow.com/a/24562019/104143)を参照してください
Peter Kofler

テストインスタンスオブジェクトからテストを実行しようとしたことがありますか?
ピノキオ2017年

25

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

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

if __name__ == "__main__":

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

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

どこuclid私のプロジェクトであり、TestSymbolsかつTestPatternsのサブクラスですTestCase


unittest.TestLoader docsから:「通常、このクラスのインスタンスを作成する必要はありません。unittestモジュールは、unittest.defaultTestLoaderとして共有できるインスタンスを提供します。」またTestSuite反復子を引数として受け入れるので、ループ内で反復子を作成して、繰り返しを回避できますloader.loadTestsFromTestCase
2ビットの錬金術師

@ 2ビットの錬金術師、特に2番目のポイントは素晴らしいです。含めるようにコードを変更しますが、テストできません。(最初のmodは、私の好みではJavaに似すぎているように見えます。私は不合理だと気づきました(キャメルケースの変数名をねじ込んでください))。
認知症のハリネズミ

これは私のお気に入りです、とてもきれいです。これをパッケージ化して、通常のコマンドラインで引数にすることができました。
MarkII 2016年

15

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 __name__ == '__main__':
    unittest.main()

次のようなファイブでの実行

Ran 27 tests in 0.187s
OK

これはpython2.7でのみ利用できると思います
Larry Cai

@larrycaiたぶん、私は通常Python 3を使用していることがあります。質問は特定のバージョンに関連付けられていませんでした。
rds

私はPython 3.4を使用しており、ループを冗長にしてスイートを返します。
砂丘

将来のためにラリーのは:「多くの新機能は、テストの発見を含め、Pythonの2.7でユニットテストに追加されました。unittest2あなたが以前のバージョンのPythonでこれらの機能を使用できるようになります。」
2ビットの錬金術師

8

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

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

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


7

以下の場合は、パッケージのライブラリやアプリケーション、あなたはそれを行うにはしたくありません。setuptools あなたのためにそれを行います

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

ルートテストパッケージの場所を次のように伝えます。

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

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

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

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

いい答えです。展開する前にテストを自動化するために使用できます。ありがとう
Arthur Clerc-Gherardi

4

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

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

import unittest

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

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

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

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


すべてのテストファイルとテストメソッドの名前は、「test_」で始まる必要があります。それ以外の場合、「Run as-> Python unit test」コマンドはそれらを見つけません。
Stefan

2

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

これは古い質問ですが、現在(2019年に)うまくいったのは次のとおりです。

python -m unittest *_test.py

すべてのテストファイルはソースファイルと同じフォルダにあり、末尾が_testです。



1

このBASHスクリプトは、現在の作業ディレクトリに関係なく、ファイルシステムの任意の場所から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制作中にパッケージ/メモリのオーバーヘッドに負担をかけるファイルは必要ありません。


-3

コマンドラインからテストを実行するラッパーを作成することによる私のアプローチは次のとおりです。

#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging

if __name__ == '__main__':
    # Parse arguments.
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
    parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
    parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
    parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
    parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
    parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Load files from the arguments.
    for filename in args.files:
        exec(open(filename).read())

    # See: http://codereview.stackexchange.com/q/88655/15346
    def make_suite(tc_class):
        testloader = unittest.TestLoader()
        testnames = testloader.getTestCaseNames(tc_class)
        suite = unittest.TestSuite()
        for name in testnames:
            suite.addTest(tc_class(name, cargs=args))
        return suite

    # Add all tests.
    alltests = unittest.TestSuite()
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj) and name.startswith("FooTest"):
            alltests.addTest(make_suite(obj))

    # Set-up logger
    verbose = bool(os.environ.get('VERBOSE', args.verbose))
    debug   = bool(os.environ.get('DEBUG', args.debug))
    if verbose or debug:
        logging.basicConfig( stream=sys.stdout )
        root = logging.getLogger()
        root.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
        root.addHandler(ch)
    else:
        logging.basicConfig(stream=sys.stderr)

    # Run tests.
    result = unittest.TextTestRunner(verbosity=2).run(alltests)
    sys.exit(not result.wasSuccessful())

簡単にするために、私の非PEP8コーディング標準を許しません

次に、すべてのテストの共通コンポーネントのBaseTestクラスを作成できるため、各テストは次のようになります。

from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
    def test_foo(self):
        driver = self.driver
        driver.get(self.base_url + "/")

実行するには、コマンドライン引数の一部としてテストを指定するだけです。例:

./run_tests.py -h http://example.com/ tests/**/*.py

2
この回答のほとんどは、テストの発見(つまり、ロギングなど)とは何の関係もありません。Stack Overflowは質問に答えるためのものであり、無関係なコードを見せびらかすものではありません。
Corey Goldberg
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.