一般的なテストディレクトリ構造でunittestを実行する


699

単純なPythonモジュールでも非常に一般的なディレクトリ構造は、ユニットテストを独自のtestディレクトリに分離することです。

new_project/
    antigravity/
        antigravity.py
    test/
        test_antigravity.py
    setup.py
    etc.

たとえば、このPythonプロジェクトhowtoを参照してください。

私の質問は単に実際にテストを実行する通常の方法は何ですか?これは私以外のすべての人には明らかだと思いますが、モジュールがパス上にないため失敗するため、python test_antigravity.pyテストディレクトリから実行することはできませんimport antigravity

PYTHONPATHやその他の検索パスに関連するトリックを変更できることはわかっていますが、それが最も簡単な方法であるとは信じられません。開発者であれば問題ありませんが、テストを確認したいだけの場合にユーザーが使用することを期待するのは現実的ではありません。通過。

もう1つの方法は、テストファイルを他のディレクトリにコピーすることだけですが、少しばかげているように思われ、最初は別のディレクトリにそれらを置く意味がありません。

それで、私の新しいプロジェクトにソースをダウンロードしたばかりの場合、ユニットテストをどのように実行しますか?ユーザーに「ユニットテストを実行するにはXを実行してください」と言ってもらいたいと思います。


5
@EMP検索パスを設定する必要がある場合の適切な解決策は...検索パスを設定することです。どのようなソリューションを期待していましたか?
Carl Meyer

7
@CarlMeyer別のより良い解決策はunittest以下の私の回答で説明されているようにコマンドラインインターフェイスを使用することです。これにより、パスにディレクトリを追加する必要がなくなります。
Pierre

13
こっちも一緒。私は小さなPythonプロジェクト用の最初の単体テストの作成に着手し、ソースをsrcディレクトリに保持し、テストをテストディレクトリに保持している間は簡単にテストを実行できないという理由で数日かかりました。一見、既存のテストフレームワークのいずれかと。私は最終的に物事を受け入れ、方法を見つけます。しかし、これは非常にイライラする導入でした。(そして私はPythonの外でユニットテストのベテランです。)
Ates Goral

回答:


655

私の意見では最良の解決策は、ディレクトリをに追加するunittest コマンドラインインターフェイスを使用するsys.pathことですTestLoader

たとえば、次のようなディレクトリ構造の場合:

new_project
├── antigravity.py
└── test_antigravity.py

あなたはただ走ることができます:

$ cd new_project
$ python -m unittest test_antigravity

あなたのようなディレクトリ構造の場合:

new_project
├── antigravity
   ├── __init__.py         # make it a package
   └── antigravity.py
└── test
    ├── __init__.py         # also make test a package
    └── test_antigravity.py

また、testパッケージ内のテストモジュールでは、antigravity通常どおりパッケージとそのモジュールをインポートできます。

# import the package
import antigravity

# import the antigravity module
from antigravity import antigravity

# or an object inside the antigravity module
from antigravity.antigravity import my_object

単一のテストモジュールの実行:

この場合、単一のテストモジュールを実行するにはtest_antigravity.py

$ cd new_project
$ python -m unittest test.test_antigravity

インポートと同じ方法でテストモジュールを参照するだけです。

単一のテストケースまたはテストメソッドの実行:

また、単一TestCaseまたは単一のテストメソッドを実行できます。

$ python -m unittest test.test_antigravity.GravityTestCase
$ python -m unittest test.test_antigravity.GravityTestCase.test_method

すべてのテストを実行する:

すべてのテストを検出して実行するテストディスカバリを使用することもできます。これらは、指定されたモジュールまたはパッケージである必要がありますtest*.py-p, --patternフラグで変更できます)。

$ cd new_project
$ python -m unittest discover
$ # Also works without discover for Python 3
$ # as suggested by @Burrito in the comments
$ python -m unittest

これによりtest*.pytestパッケージ内のすべてのモジュールが実行されます。


53
python -m unittest discovertest名前が付けられている場合、ディレクトリでテストを見つけて実行しますtest*.py。サブディレクトリtestsに名前を付けた場合は、を使用しpython -m unittest discover -s tests、テストファイルantigravity_test.pyに名前を付けた場合は、python -m unittest discover -s tests -p '*test.py' ファイル名にアンダースコアを使用できますが、ダッシュは使用できません。
Mike3d0g

10
ImportError: No module named 'test.test_antigravity'unittestライブラリのテストサブモジュールとの競合が原因で、Python 3でこれがエラーで失敗します。たぶん、専門家が回答のサブディレクトリ名を確認して、たとえば「tests」(複数)に変更できます。
expz

9
私はtest_antigravity.pyまだ両方のインポートエラーをスローimport antigravityしてfrom antigravity import antigravityも、。両方の__init_.pyファイルがありpython3 -m unittest discovernew projectディレクトリから呼び出しています。他に何が間違っているのでしょうか?
imrek

19
test/__init__.py空であっても、ファイルはここで重要です
フランソワ

3
@ Mike3d0gは、ディレクトリ名testが特別であることを暗示するつもりだったのかどうかはわかりませんが、記録のためだけではありません。:P python -m unittest discoverはと同様にテストファイルでtests/も機能しtest/ます。
ライアン、

49

ユーザーにとって最も簡単な解決策は、runtests.py必要に応じてルートプロジェクトディレクトリをsys.path一時的に追加するなど、必要なテスト環境をブートストラップする実行可能スクリプト(など)を提供することです。これは、ユーザーが環境変数を設定する必要がないため、次のようなものはブートストラップスクリプトで正常に機能します。

import sys, os

sys.path.insert(0, os.path.dirname(__file__))

そうすれば、ユーザーへの指示は「python runtests.py」のように簡単になります。

もちろん、本当に必要なパスがである場合はos.path.dirname(__file__)sys.pathまったく追加する必要はありません。Pythonは常に、現在実行中のスクリプトのディレクトリをの最初にsys.path配置するため、ディレクトリ構造によってruntests.pyは、適切な場所にを配置するだけで十分な場合があります。

また、Python 2.7+のunittestモジュール(Python 2.6以前ではunittest2としてバックポートされています)にテストディスカバリが組み込まれているため、自動テストディスカバリが必要な場合はノーズは不要になりました。ユーザーの指示は次のように簡単です。python -m unittest discover


「メジャーメジャー」のようなサブフォルダーにいくつかのテストを配置します。それらはpython -m unittest discoverで実行できますが、そのうちの1つだけを実行するように選択するにはどうすればよいですか。python -m unittest tests / testxxxxxを実行すると、パスの問題で失敗します。dicoveryモードがすべてを解決しているので、私はあなたが最初の点で示唆パスの修正をhandcodingせずに、パスの問題を解決するために、別のトリックがあることを期待
フレデリック・バザン

2
@FredericBazin単一のテストまたはテストファイルのみが必要な場合は検出を使用せず、実行するモジュールに名前を付けます。(ファイルパスではなく)モジュールのドットパスとして名前を付けると、検索パスを正しく把握できます。詳細については、ピーターの回答を参照してください。
カールマイヤー

このハックは、私がのようなものを実行しなければならないシナリオで役立ちましたpython -m pdb tests\test_antigravity.py。pdb内でsys.path.insert(0, "antigravity")、モジュールを実行しているかのようにインポートステートメントを解決できるように実行しました。
ixe013

23

私は通常、プロジェクトディレクトリ(ソースディレクトリとソースディレクトリの両方に共通のスクリプト)に「テストの実行」スクリプトを作成します test、「すべてのテスト」スイートをロードます。これは通常、定型コードなので、プロジェクト間で再利用できます。

run_tests.py:

import unittest
import test.all_tests
testSuite = test.all_tests.create_test_suite()
text_runner = unittest.TextTestRunner().run(testSuite)

test / all_tests.py(ディレクトリ内のすべてのPythonユニットテストを実行するにどうすればよいですか?

import glob
import unittest

def create_test_suite():
    test_file_strings = glob.glob('test/test_*.py')
    module_strings = ['test.'+str[5:len(str)-3] for str in test_file_strings]
    suites = [unittest.defaultTestLoader.loadTestsFromName(name) \
              for name in module_strings]
    testSuite = unittest.TestSuite(suites)
    return testSuite

この設定を使用すると、実際にinclude antigravityテストモジュールでのみ実行できます。欠点は、特定のテストを実行するためにサポートコードがさらに必要になることです...毎回実行するだけです。


1
また、私は望んでいたプロジェクトディレクトリ内のスクリプトを、見つかったたくさんのクリーナーの方法、それを行うために。強くお勧めします。run tests
z33k

18

リンクした記事から:

test_modulename.pyファイルを作成し、その中にユニットテストテストを配置します。テストモジュールはコードとは別のディレクトリにあるため、モジュールを実行するには、モジュールの親ディレクトリをPYTHONPATHに追加する必要がある場合があります。

$ cd /path/to/googlemaps

$ export PYTHONPATH=$PYTHONPATH:/path/to/googlemaps/googlemaps

$ python test/test_googlemaps.py

最後に、もう1つの人気のあるPythonの単体テストフレームワーク(重要です!)、鼻があります。noseは、組み込みの単体テストフレームワークを簡略化および拡張するのに役立ちます(たとえば、テストコードを自動的に見つけてPYTHONPATHを設定することができます)が、標準のPythonディストリビューションには含まれていません。

おそらくそれが示唆するようにあなたはを見る必要がありますか?


3
はい、これは機能します(私にとっては)が、私はユーザーにテストを実行させるためにユーザーにモジュールに与えることができる最も簡単な指示を本当に求めています。パスを変更することは実際にはそれかもしれませんが、もっと簡単なものを探しています。
メジャーメジャー

4
では、100のプロジェクトに取り組んだ後のpythonパスはどのようになるでしょうか?手動でパスをクリーンアップする必要がありますか?もしそうなら、これはいやらしいデザインです!
jeremyjjbrown 14年

11

別の単体テストフォルダーを使用して、同じ問題が発生しました。上記の提案から、絶対ソースパスをに追加しsys.pathます。

次のソリューションの利点は、test/test_yourmodule.py最初にtest-directoryに変更せずにファイルを実行できることです。

import sys, os
testdir = os.path.dirname(__file__)
srcdir = '../antigravity'
sys.path.insert(0, os.path.abspath(os.path.join(testdir, srcdir)))

import antigravity
import unittest

9

「python setup.pydevelop」を実行すると、パッケージはパスに含まれます。しかし、システムのpythonインストールに感染する可能性があるため、これを実行したくない場合があります。virtualenvbuildoutなどのツールが存在するのはこのためです。


7

Pythonユニットテストモジュールのソリューション/例

次のプロジェクト構造があるとします。

ProjectName
 ├── project_name
 |    ├── models
 |    |    └── thing_1.py
 |    └── __main__.py
 └── test
      ├── models
      |    └── test_thing_1.py
      └── __main__.py

python project_nameを呼び出すを使用して、ルートディレクトリからプロジェクトを実行できますProjectName/project_name/__main__.py


でテストを実行するにはpython test、効果的に実行ProjectName/test/__main__.pyするには、次のことを行う必要があります。

1)あなたのターンtest/models追加することによって、パッケージにディレクトリを__init__.pyファイルを。これにより、サブディレクトリ内のテストケースが親testディレクトリからアクセス可能になります。

# ProjectName/test/models/__init__.py

from .test_thing_1 import Thing1TestCase        

2)システムパスを変更してtest/__main__.pyproject_nameディレクトリを含めます。

# ProjectName/test/__main__.py

import sys
import unittest

sys.path.append('../project_name')

loader = unittest.TestLoader()
testSuite = loader.discover('test')
testRunner = unittest.TextTestRunner(verbosity=2)
testRunner.run(testSuite)

これproject_nameで、テストからのものを正常にインポートできます。

# ProjectName/test/models/test_thing_1.py    

import unittest
from project_name.models import Thing1  # this doesn't work without 'sys.path.append' per step 2 above

class Thing1TestCase(unittest.TestCase):

    def test_thing_1_init(self):
        thing_id = 'ABC'
        thing1 = Thing1(thing_id)
        self.assertEqual(thing_id, thing.id)

5

を使用setup.py developして、作業ディレクトリをインストール済みのPython環境の一部にしてから、テストを実行します。


これは私を取得し、invalid command 'develop'私が求める場合、このオプションは言及されていませんsetup.py --help-commands。これが機能するには、setup.pyそれ自体に何かが必要ですか?
メジャーメジャー

それは大丈夫です-問題は私がimport setuptools私のsetup.pyファイルから欠落していたことでした。しかし、これは他の人のモジュールではこれが常に機能するとは限らないことを示していると思います。
メジャーメジャー

1
あなたが持っている場合はピップを、あなたにはあなたのパッケージをインストールするためにそれを使用することができ、「編集可能」モードpip install -e .これは、同様に、それがどこにある編集し、それを継続することができ、ソースをコピーせずにPython環境にパッケージを追加します。
Eric Smith

pip install -e .python setup.py developはとまったく同じですが、setup.py実際には使用しない場合でもsetuptoolsを使用するようにモンキーパッチを適用するだけなので、どちらの方法でも機能します。
カールマイヤー

5

VS Codeを使用していて、テストがプロジェクトと同じレベルにある場合、コードの実行とデバッグはそのままでは機能しません。あなたができることはあなたのlaunch.jsonファイルを変更することです:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python",
            "type": "python",
            "request": "launch",
            "stopOnEntry": false,
            "pythonPath": "${config:python.pythonPath}",
            "program": "${file}",
            "cwd": "${workspaceRoot}",
            "env": {},
            "envFile": "${workspaceRoot}/.env",
            "debugOptions": [
                "WaitOnAbnormalExit",
                "WaitOnNormalExit",
                "RedirectOutput"
            ]
        }    
    ]
}

ここで重要なのはenvFileです

"envFile": "${workspaceRoot}/.env",

プロジェクトのルートに.envファイルを追加します

.envファイル内に、プロジェクトのルートへのパスを追加します。これは一時的に追加されます

PYTHONPATH = C:\ YOUR \ PYTHON \ PROJECT \ ROOT_DIRECTORY

あなたのプロジェクトへのパスとVSコードからデバッグユニットテストを使用できるようになります


5

「src」ディレクトリからunittestコマンドラインインターフェースを実行すると、インポートは変更せずに正しく機能することに気づきました。

python -m unittest discover -s ../test

これをプロジェクトディレクトリのバッチファイルに配置する場合は、次のようにします。

setlocal & cd src & python -m unittest discover -s ../test

5

私は長い間同じ問題を抱えてきました。私が最近選択したのは、次のディレクトリ構造です。

project_path
├── Makefile
├── src
   ├── script_1.py
   ├── script_2.py
   └── script_3.py
└── tests
    ├── __init__.py
    ├── test_script_1.py
    ├── test_script_2.py
    └── test_script_3.py

そして__init__.py、testフォルダーのスクリプトに、次のように記述します。

import os
import sys
PROJECT_PATH = os.getcwd()
SOURCE_PATH = os.path.join(
    PROJECT_PATH,"src"
)
sys.path.append(SOURCE_PATH)

スクリプトの適切な実行を強制するため、プロジェクトを共有するために非常に重要なのはMakefileです。Makefileに入れたコマンドは次のとおりです。

run_tests:
    python -m unittest discover .

Makefileは、実行するコマンドだけでなく、 、から実行する場所のです。テストでcdを実行して実行した場合、unit_tests python -m unittest discover .initスクリプトがos.getcwd()を呼び出すため、機能しません。これにより、誤った絶対パス(sys.pathに追加され、絶対パスが指定されます)がポイントされます。ソースフォルダ)。Discoverはすべてのテストを検出するため、スクリプトは実行されますが、適切に実行されません。Makefileは、この問題を覚えておく必要がないようにするためのものです。

srcフォルダー、ユニットテスト、環境変数に触れる必要がなく、すべてがスムーズに実行されるため、このアプローチが本当に好きです。

気に入ったら教えてください。

お役に立てれば幸いです。


4

以下は私のプロジェクト構造です:

ProjectFolder:
 - project:
     - __init__.py
     - item.py
 - tests:
     - test_item.py

setUp()メソッドでインポートする方がよいことがわかりました。

import unittest
import sys    

class ItemTest(unittest.TestCase):

    def setUp(self):
        sys.path.insert(0, "../project")
        from project import item
        # further setup using this import

    def test_item_props(self):
        # do my assertions

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

4

実際にテストを実行する通常の方法は何ですか

Python 3.6.2を使用しています

cd new_project

pytest test/test_antigravity.py

pytestをインストールするには:sudo pip install pytest

パス変数を設定しなかったため、同じ「テスト」プロジェクト構造でインポートが失敗していません。

私はこれをコメントアウトしました:if __name__ == '__main__'このように:

test_antigravity.py

import antigravity

class TestAntigravity(unittest.TestCase):

    def test_something(self):

        # ... test stuff here


# if __name__ == '__main__':
# 
#     if __package__ is None:
# 
#         import something
#         sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
#         from .. import antigravity
# 
#     else:
# 
#         from .. import antigravity
# 
#     unittest.main()

4

選択したテストまたはすべてのテストを実行するラッパーを使用することが可能です。

例えば:

./run_tests antigravity/*.py

または、すべてのテストを再帰的に実行するには、グロビングtests/**/*.py)を使用します(によって有効化shopt -s globstar)。

ラッパーは基本的にargparse、次のような引数を解析するために使用できます。

parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*')

次に、すべてのテストをロードします。

for filename in args.files:
    exec(open(filename).read())

次に、それらをテストスイートに追加します(を使用inspect)。

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

そしてそれらを実行します:

result = unittest.TextTestRunner(verbosity=2).run(alltests)

詳細については、この例を確認してください。

参照:ディレクトリですべてのPythonユニットテストを実行する方法


4

Python 3以降

@Pierreに追加

次のunittestようなディレクトリ構造を使用します。

new_project
├── antigravity
   ├── __init__.py         # make it a package
   └── antigravity.py
└── test
    ├── __init__.py         # also make test a package
    └── test_antigravity.py

テストモジュールを実行するにはtest_antigravity.py

$ cd new_project
$ python -m unittest test.test_antigravity

またはシングル TestCase

$ python -m unittest test.test_antigravity.GravityTestCase

必須忘れてはいけない__init__.pyそう仕事ない場合でも、空を。


2

ブードゥーがなければ、親ディレクトリからインポートすることはできません。これは、少なくともPython 3.6で機能する別の方法です。

最初に、次の内容のファイルtest / context.pyを用意します。

import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

次に、test / test_antigravity.pyファイルに次のインポートを含めます。

import unittest
try:
    import context
except ModuleNotFoundError:
    import test.context    
import antigravity

このtry-except句の理由は、

  • 「python test_antigravity.py」で実行すると、test.contextのインポートが失敗し、
  • new_projectディレクトリから「python -m unittest」で実行すると、インポートコンテキストが失敗します。

この策略で、彼らは両方とも働きます。

これで実行できます で、testディレクトリ内のすべてのテストファイルを。

$ pwd
/projects/new_project
$ python -m unittest

または、次のコマンドで個々のテストファイルを実行します。

$ cd test
$ python test_antigravity

わかりました、test_antigravity.py内にcontext.pyのコンテンツを含めることほどきれいではありませんが、少しかもしれません。提案は大歓迎です。


2

テストディレクトリに複数のディレクトリがある場合は、各ディレクトリに__init__.pyファイルを追加する必要があります。

/home/johndoe/snakeoil
└── test
    ├── __init__.py        
    └── frontend
        └── __init__.py
        └── test_foo.py
    └── backend
        └── __init__.py
        └── test_bar.py

次に、すべてのテストを一度に実行するには、次のコマンドを実行します。

python -m unittest discover -s /home/johndoe/snakeoil/test -t /home/johndoe/snakeoil

ソース: python -m unittest -h

  -s START, --start-directory START
                        Directory to start discovery ('.' default)
  -t TOP, --top-level-directory TOP
                        Top level directory of project (defaults to start
                        directory)

1

このBASHスクリプトは、作業中のディレクトリに関係なく、ファイルシステムのどこからでもpython unittestテストディレクトリを実行します。

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

#!/bin/bash

this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

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

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


1

この方法では、コマンドラインからシステム変数をいじる必要なく、どこからでもテストスクリプトを実行できます。

これにより、メインプロジェクトフォルダーがpythonパスに追加され、現在の作業ディレクトリではなく、スクリプト自体に相対的な場所が見つかります。

import sys, os

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

それをすべてのテストスクリプトの先頭に追加します。これにより、メインプロジェクトフォルダーがシステムパスに追加され、そこから機能するすべてのモジュールインポートが機能するようになります。そして、どこからテストを実行するかは問題ではありません。

もちろん、project_path_hackファイルを変更して、メインプロジェクトフォルダーの場所と一致させることができます。


0

コマンドラインのみのソリューションを探している場合:

次のディレクトリ構造に基づく(専用のソースディレクトリで一般化):

new_project/
    src/
        antigravity.py
    test/
        test_antigravity.py

Windows:(new_project

$ set PYTHONPATH=%PYTHONPATH%;%cd%\src
$ python -m unittest discover -s test

これをバッチのforループで使用する場合は、この質問を参照してください。

Linux:(new_project

$ export PYTHONPATH=$PYTHONPATH:$(pwd)/src  [I think - please edit this answer if you are a Linux user and you know this]
$ python -m unittest discover -s test

このアプローチでは、必要に応じてPYTHONPATHにさらにディレクトリを追加することもできます。


0

実際には、pipツールを使用する必要があります。

pip install -e .開発モードでパッケージをインストールするために使用します。これは非常に良い方法であり、pytestによって推奨されています(従うべき2つのプロジェクトレイアウトも見つけることができる、それらの良い方法のドキュメントを参照してください)。


なぜこの回答に反対票を投じるのですか?受け入れられた回答を読みましたが、悪くはありませんでしたが、pytestスタックトレース情報と詳細なアサーションエラー情報を含むコンソール出力がカラーで表示されるため、テストを実行する方がはるかに優れています。
aliopi 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.