「pythonsetup.pytest」からunittestdiscoverを実行する方法は?


82

python setup.py test相当するものを実行する方法を理解しようとしていますpython -m unittest discover。run_tests.pyスクリプトを使用したくないし、外部テストツール(noseまたはなどpy.test)も使用したくない。ソリューションがPython2.7でのみ機能する場合は問題ありません。

で、configのフィールドやフィールドにsetup.py何かを追加する必要があると思いますが、正しく機能する組み合わせが見つからないようです。test_suitetest_loader

config = {
    'name': name,
    'version': version,
    'url': url,
    'test_suite': '???',
    'test_loader': '???',
}

これは、unittestPython 2.7に組み込まれているものだけを使用して可能ですか?

参考までに、私のプロジェクト構造は次のようになります。

project/
  package/
    __init__.py
    module.py
  tests/
    __init__.py
    test_module.py
  run_tests.py <- I want to delete this
  setup.py

更新:これはで可能ですがunittest2、私はだけを使用して同等のものを見つけたいですunittest

https://pypi.python.org/pypi/unittest2から

unittest2には、非常に基本的なsetuptools互換のテストコレクターが含まれています。setup.pyでtest_suite = 'unittest2.collector'を指定します。これにより、setup.pyを含むディレクトリのデフォルトパラメータを使用してテスト検出が開始されるため、例として最も役立つ可能性があります(unittest2 / collector.pyを参照)。

今のところ、私はと呼ばれるスクリプトを使用していますrun_tests.pyが、を使用するソリューションに移行することでこれを取り除くことができると期待していますpython setup.py test

ここだrun_tests.py、私は削除するために願っています:

import unittest

if __name__ == '__main__':

    # use the default shared TestLoader instance
    test_loader = unittest.defaultTestLoader

    # use the basic test runner that outputs to sys.stderr
    test_runner = unittest.TextTestRunner()

    # automatically discover all tests in the current dir of the form test*.py
    # NOTE: only works for python 2.7 and later
    test_suite = test_loader.discover('.')

    # run the test suite
    test_runner.run(test_suite)

たまたまここに来た人には注意が必要です。setup.pyテストはコード「臭い」と見なされ、非推奨になるように設定されています。 github.com/pytest-dev/pytest-runner/issues/50
YashashGaurav20年

回答:


44

py27 +またはpy32 +を使用する場合、解決策は非常に簡単です。

test_suite="tests",

1
これがうまく機能することを望みます、私はこの問題に遭遇しました:stackoverflow.com/questions/6164004/… "テスト名はモジュール名と一致する必要があります。" foo_test.py "テストがある場合、対応するモジュールfoo.pyが必要です。」
Charles L.

1
同意する。私の場合、文字通り.pyを持つそのようなPythonモジュールがない外部のPythonをテストしている場合、これを達成するための良い方法はないようです。
トムスワーリー2016年

2
これが正しい解決策です。@CharlesLの問題はありませんでした。持っていました。私のテストはすべてtest_*.py。という名前です。さらに、指定されたディレクトリを実際に再帰的に検索して、を拡張するクラスを見つけることがわかりましたunittest.TestCast。あなたはあなたの中に持っているディレクトリ構造を持っている場合、これは非常に有用であるtests/first_batch/test_*.pytests/second_batch/test_*.py。指定するだけでtest_suite="tests",、すべてを再帰的に取得します。ネストされた各ディレクトリには__init__.pyファイルが必要であることに注意してください。
dcmm88 2017

39

Setuptoolsを使用したパッケージの構築と配布から(私の強調):

test_suite

unittest.TestCaseサブクラス(またはそれらの1つ以上を含むパッケージまたはモジュール、あるいはそのようなサブクラスのメソッド)に名前を付ける文字列、または引数なしで呼び出すことができ、unittest.TestSuiteを返す関数に名前を付ける文字列 。

したがって、でsetup.pyTestSuiteを返す関数を追加します。

import unittest
def my_test_suite():
    test_loader = unittest.TestLoader()
    test_suite = test_loader.discover('tests', pattern='test_*.py')
    return test_suite

次に、setup次のようにコマンドを指定します。

setup(
    ...
    test_suite='setup.my_test_suite',
    ...
)

3
このソリューションには2つの「レベル」のユニットテストが作成されるため、問題があります。つまり、setuptoolsは、setup.my_test_suiteからTestSuiteを作成しようとする「test」コマンドを作成します。これにより、setup.pyがインポートされ、setup()が再度実行されます。今回は、目的のテストを実行する新しい(ネストされた)テストコマンドを作成します。これはほとんどの人には気付かないかもしれませんが、テストコマンドを拡張しようとすると(テストを「インプレース」で実行できないため、変更する必要がありました)、奇妙な問題が発生する可能性があります。代わりにstackoverflow.com/a/21726329/3272850を使用してください
dcmm88 2017

2
これにより、上記の理由でテストが2回実行されます。関数を__init__.pytestsフォルダーのに移動し、それを参照することで修正しました。
匿名

3
テストが2回実行される問題は、スクリプトのブロック内でsetup()関数を実行することで簡単に修正できます。セットアップスクリプトが初めて実行されるときなので、ifブロックが呼び出されます。2回目は、セットアップスクリプトがモジュールとしてインポートされるため、ifブロックは呼び出されません。if __name__ == '__main__':setup.py
2018

うーん、setup.pyにそのtest_suiteパラメーターがまったく含まれていないことに気付きましたが、「pythonsetup.pytest」は引き続き正常に機能します。これは、ドキュメントの内容とは異なります。「setup()呼び出しでtest_suiteを設定せず、-test-suiteオプションを指定しないと、エラーが発生します。」何か案が?
RayLuo

21

これを機能させるために設定は必要ありません。それを行うには、基本的に2つの主な方法があります。

手っ取り早い方法

名前をに変更するtest_module.pymodule_test.py(基本的に_test特定のモジュールのテストのサフィックスとして追加されます)、Pythonはそれを自動的に検出します。必ずこれをに追加してsetup.pyください:

from setuptools import setup, find_packages

setup(
    ...
    test_suite = 'tests',
    ...
)

長い道のり

現在のディレクトリ構造でこれを行う方法は次のとおりです。

project/
  package/
    __init__.py
    module.py
  tests/
    __init__.py
    test_module.py
  run_tests.py <- I want to delete this
  setup.py

の下tests/__init__.pyで、unittestと単体テストスクリプトをインポートしtest_module、テストを実行する関数を作成します。にtests/__init__.py、次のように入力します。

import unittest
import test_module

def my_module_suite():
    loader = unittest.TestLoader()
    suite = loader.loadTestsFromModule(test_module)
    return suite

このTestLoaderクラスには、以外にも他の関数がありloadTestsFromModuleます。走っdir(unittest.TestLoader)て他のものを見ることができますが、これが最も簡単に使用できます。

あなたのディレクトリ構造はそのようなものなので、おそらくあなたはtest_moduleあなたのmoduleスクリプトをインポートできるようにしたいと思うでしょう。すでにこれを行っている可能性がありますが、行っていない場合に備えて、packageモジュールとmoduleスクリプトをインポートできるように親パスを含めることができます。の上部に、次のようtest_module.pyに入力します。

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

import unittest
import package.module
...

最後に、にモジュールをsetup.py含めて、tests作成したコマンドを実行しますmy_module_suite

from setuptools import setup, find_packages

setup(
    ...
    test_suite = 'tests.my_module_suite',
    ...
)

次に、を実行しpython setup.py testます。

これは誰かが参考として作ったサンプルです。


2
問題は、「pythonsetup.pytest」にunittestの検出機能を使用させる方法でした。これはまったく対処していません。
ミケネロン

うーん...ええ、私は完全に質問が何か違うことを尋ねていると思いました。それがどのように起こったのかわかりません、私は私の心を失っているに違いありません:(
反物質

5

考えられる解決策の1つはtestdistutilsand setuptools/のコマンドを単純に拡張することdistributeです。これは完全なクルージのようで、私が望むよりもはるかに複雑ですが、実行時にパッケージ内のすべてのテストを正しく検出して実行するようpython setup.py testです。誰かがよりエレガントな解決策を提供することを期待して、私の質問への答えとしてこれを選択することを延期しています:)

https://docs.pytest.org/en/latest/goodpractices.html#integrating-with-setuptools-python-setup-py-test-pytest-runnerに触発されました)

setup.py

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

def discover_and_run_tests():
    import os
    import sys
    import unittest

    # get setup.py directory
    setup_file = sys.modules['__main__'].__file__
    setup_dir = os.path.abspath(os.path.dirname(setup_file))

    # use the default shared TestLoader instance
    test_loader = unittest.defaultTestLoader

    # use the basic test runner that outputs to sys.stderr
    test_runner = unittest.TextTestRunner()

    # automatically discover all tests
    # NOTE: only works for python 2.7 and later
    test_suite = test_loader.discover(setup_dir)

    # run the test suite
    test_runner.run(test_suite)

try:
    from setuptools.command.test import test

    class DiscoverTest(test):

        def finalize_options(self):
            test.finalize_options(self)
            self.test_args = []
            self.test_suite = True

        def run_tests(self):
            discover_and_run_tests()

except ImportError:
    from distutils.core import Command

    class DiscoverTest(Command):
        user_options = []

        def initialize_options(self):
                pass

        def finalize_options(self):
            pass

        def run(self):
            discover_and_run_tests()

config = {
    'name': 'name',
    'version': 'version',
    'url': 'http://example.com',
    'cmdclass': {'test': DiscoverTest},
}

setup(**config)

3

http://hg.python.org/unittest2/file/2b6411b9a838/unittest2/collector.pyにわずかに触発されたもう1つの理想的とは言えないソリューション

TestSuite検出されたテストのを返すモジュールを追加します。次に、そのモジュールを呼び出すようにセットアップを構成します。

project/
  package/
    __init__.py
    module.py
  tests/
    __init__.py
    test_module.py
  discover_tests.py
  setup.py

ここにありdiscover_tests.pyます:

import os
import sys
import unittest

def additional_tests():
    setup_file = sys.modules['__main__'].__file__
    setup_dir = os.path.abspath(os.path.dirname(setup_file))
    return unittest.defaultTestLoader.discover(setup_dir)

そしてここにありますsetup.py

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

config = {
    'name': 'name',
    'version': 'version',
    'url': 'http://example.com',
    'test_suite': 'discover_tests',
}

setup(**config)

3

Pythonの標準ライブラリunittestモジュールは検出をサポートします(Python 2.7以降、およびPython 3.2以降)。これらの最小バージョンを想定できる場合は、discoverコマンドライン引数をコマンドに追加するだけunittestです。

ほんの少しの調整が必要ですsetup.py

import setuptools.command.test
from setuptools import (find_packages, setup)

class TestCommand(setuptools.command.test.test):
    """ Setuptools test command explicitly using test discovery. """

    def _test_args(self):
        yield 'discover'
        for arg in super(TestCommand, self)._test_args():
            yield arg

setup(
    ...
    cmdclass={
        'test': TestCommand,
    },
)

ところで、質問は特にこの機能に関するものなので、実際に検出をサポートするPythonバージョン(2.7および3.2以降)のみをターゲットにしていると仮定しています。もちろん、挿入物をバージョンでラップすることもできます-古いバージョンとの互換性を維持したい場合はチェックしてください(したがって、そのような場合はsetuptoolsの標準ローダーを使用します)。
ミケネロン2014年

0

これはrun_tests.pyを削除しませんが、setuptoolsで機能するようになります。追加:

class Loader(unittest.TestLoader):
    def loadTestsFromNames(self, names, _=None):
        return self.discover(names[0])

次に、setup.pyで:(私はあなたが次のようなことをしていると思いますsetup(**config)

config = {
    ...
    'test_loader': 'run_tests:Loader',
    'test_suite': '.', # your start_dir for discover()
}

私が見る唯一の欠点は、のセマンティクスを曲げることですloadTestsFromNamesが、setuptools testコマンドが唯一のコンシューマーであり、指定された方法でそれを呼び出します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.