Próbuję dowiedzieć się, jak python setup.py test
uruchomić odpowiednik python -m unittest discover
. Nie chcę używać skryptu run_tests.py i nie chcę używać żadnych zewnętrznych narzędzi testowych (takich jak nose
lub py.test
). W porządku, jeśli rozwiązanie działa tylko w Pythonie 2.7.
W setup.py
, myślę, że trzeba dodać coś do test_suite
i / lub test_loader
pól w config, ale nie wydaje się znaleźć kombinację, która działa prawidłowo:
config = {
'name': name,
'version': version,
'url': url,
'test_suite': '???',
'test_loader': '???',
}
Czy jest to możliwe przy użyciu tylko unittest
wbudowanego w Python 2.7?
FYI, struktura mojego projektu wygląda następująco:
project/
package/
__init__.py
module.py
tests/
__init__.py
test_module.py
run_tests.py <- I want to delete this
setup.py
Aktualizacja : Jest to możliwe z, unittest2
ale chcę znaleźć coś równoważnego używając tylkounittest
Z https://pypi.python.org/pypi/unittest2
unittest2 zawiera bardzo podstawowy kolektor testów kompatybilny z setuptools. Określ test_suite = 'unittest2.collector' w swoim setup.py. Rozpoczyna to wykrywanie testowe z domyślnymi parametrami z katalogu zawierającego setup.py, więc jest to prawdopodobnie najbardziej przydatne jako przykład (zobacz unittest2 / collector.py).
Na razie używam tylko skryptu o nazwie run_tests.py
, ale mam nadzieję, że mogę się tego pozbyć, przechodząc do rozwiązania, które tylko używa python setup.py test
.
Oto plik, run_tests.py
który mam nadzieję usunąć:
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)
Odpowiedzi:
Jeśli używasz py27 + lub py32 +, rozwiązanie jest dość proste:
test_suite="tests",
źródło
test_*.py
. Ponadto dowiedziałem się, że faktycznie będzie rekurencyjnie przeszukiwać podany katalog, aby znaleźć każdą klasę, która rozszerzaunittest.TestCast
. Jest to niezwykle przydatne, jeśli masz strukturę katalogów, w której masztests/first_batch/test_*.py
itests/second_batch/test_*.py
. Możesz po prostu określić,test_suite="tests",
a wszystko będzie odbierane rekurencyjnie. Zauważ, że każdy zagnieżdżony katalog będzie musiał zawierać__init__.py
plik.Z tworzenia i dystrybucji pakietów za pomocą Setuptools (moje wyróżnienie ):
W związku
setup.py
z tym dodałbyś funkcję, która zwraca TestSuite:import unittest def my_test_suite(): test_loader = unittest.TestLoader() test_suite = test_loader.discover('tests', pattern='test_*.py') return test_suite
Następnie należy określić polecenie
setup
w następujący sposób:setup( ... test_suite='setup.my_test_suite', ... )
źródło
__init__.py
folderu testów i odnosząc się do tego.setup()
funkcję wewnątrzif __name__ == '__main__':
bloku wsetup.py
skrypcie. Gdy skrypt instalacyjny jest wykonywany po raz pierwszy, zostanie wywołany blok if; za drugim razem skrypt instalacyjny zostanie zaimportowany jako moduł, więc blok if nie zostanie wywołany.test_suite
w ogóle tego parametru, ale „python setup.py test” nadal działa dobrze. Różni się to od tego, co mówi dokumentacja : „Jeśli nie ustawiłeś zestawu test_suite w wywołaniu setup () i nie podasz opcji --test-suite, wystąpi błąd”. Dowolny pomysł?Nie potrzebujesz konfiguracji, aby to działało. Zasadniczo można to zrobić na dwa główne sposoby:
Szybki sposób
Zmień nazwę
test_module.py
namodule_test.py
(w zasadzie dodaj_test
jako sufiks do testów dla konkretnego modułu), a python znajdzie go automatycznie. Po prostu dodaj to dosetup.py
:from setuptools import setup, find_packages setup( ... test_suite = 'tests', ... )
Długa droga
Oto jak to zrobić z aktualną strukturą katalogów:
W obszarze
tests/__init__.py
chcesz zaimportowaćunittest
skrypt testów jednostkowychtest_module
i, a następnie utworzyć funkcję do uruchamiania testów. Wtests/__init__.py
wpisać w coś takiego:import unittest import test_module def my_module_suite(): loader = unittest.TestLoader() suite = loader.loadTestsFromModule(test_module) return suite
TestLoader
Klasa ma inne funkcje opróczloadTestsFromModule
. Możesz pobiec,dir(unittest.TestLoader)
aby zobaczyć pozostałe, ale ten jest najprostszy w użyciu.Ponieważ twoja struktura katalogów jest taka, prawdopodobnie będziesz chciał,
test_module
aby móc zaimportować twójmodule
skrypt. Być może już to zrobiłeś, ale na wypadek, gdybyś tego nie zrobił, możesz dołączyć ścieżkę nadrzędną, aby móc zaimportowaćpackage
moduł imodule
skrypt. U górytest_module.py
wpisz:import os, sys sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) import unittest import package.module ...
Na koniec
setup.py
dołącztests
moduł i uruchom utworzone poleceniemy_module_suite
:from setuptools import setup, find_packages setup( ... test_suite = 'tests.my_module_suite', ... )
Wtedy po prostu biegniesz
python setup.py test
.Oto próbka, którą ktoś podał jako odniesienie.
źródło
Jednym z możliwych rozwiązań jest po prostu rozszerzenie
test
polecenia dladistutils
isetuptools
/distribute
. Wydaje się, że jest to totalna klęska i znacznie bardziej skomplikowana niż bym chciał, ale wydaje się, że poprawnie wykrywa i uruchamia wszystkie testy w moim pakiecie po uruchomieniupython setup.py test
. Odkładam wybór tego jako odpowiedzi na moje pytanie w nadziei, że ktoś zaproponuje bardziej eleganckie rozwiązanie :)(Zainspirowany https://docs.pytest.org/en/latest/goodpractices.html#integrating-with-setuptools-python-setup-py-test-pytest-runner )
Przykład
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)
źródło
Kolejne mniej niż idealne rozwiązanie, nieco inspirowane http://hg.python.org/unittest2/file/2b6411b9a838/unittest2/collector.py
Dodaj moduł, który zwraca
TestSuite
liczbę wykrytych testów. Następnie skonfiguruj Instalatora, aby wywołać ten moduł.Oto
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)
A oto
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)
źródło
unittest
Moduł biblioteki standardowej Pythona obsługuje wykrywanie (w Pythonie 2.7 i nowszych oraz Python 3.2 i nowszych). Jeśli możesz założyć te minimalne wersje, możesz po prostu dodaćdiscover
argument wiersza poleceń dounittest
polecenia.Wystarczy niewielka poprawka, aby
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, }, )
źródło
Nie usunie to run_tests.py, ale sprawi, że będzie działać z setuptools. Dodaj:
class Loader(unittest.TestLoader): def loadTestsFromNames(self, names, _=None): return self.discover(names[0])
Następnie w setup.py: (zakładam, że robisz coś takiego
setup(**config)
)config = { ... 'test_loader': 'run_tests:Loader', 'test_suite': '.', # your start_dir for discover() }
Jedynym minusem, jaki widzę, jest naginanie semantyki
loadTestsFromNames
, ale polecenie testowe setuptools jest jedynym konsumentem i wywołuje je w określony sposób .źródło