Wie führe ich alle Python-Komponententests in einem Verzeichnis aus?
-
20-09-2019 - |
Frage
Ich habe ein Verzeichnis, das meine Python-Komponententests enthält.Jedes Unit-Test-Modul hat die Form test_*.py.Ich versuche, eine Datei mit dem Namen zu erstellen all_test.py Das wird, wie Sie es erraten haben, alle Dateien in der oben genannten Testform ausführen und das Ergebnis zurückgeben.Ich habe bisher zwei Methoden ausprobiert;beide sind gescheitert.Ich werde die beiden Methoden zeigen und hoffe, dass jemand da draußen weiß, wie man das tatsächlich richtig macht.
Bei meinem ersten mutigen Versuch dachte ich: „Wenn ich einfach alle meine Testmodule in die Datei importiere und diese dann aufrufe.“ unittest.main()
Doodad, es wird funktionieren, oder?“ Nun, es stellte sich heraus, dass ich falsch lag.
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()
Das hat nicht funktioniert, das Ergebnis, das ich bekam, war:
$ python all_test.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Für meinen zweiten Versuch denke ich, ok, vielleicht werde ich versuchen, die ganze Testsache auf eine „manuellere“ Art und Weise durchzuführen.Also habe ich versucht, das unten zu tun:
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()
Das hat auch nicht funktioniert, aber es scheint so nah dran zu sein!
$ 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
Ich scheine eine Art Suite zu haben und kann das Ergebnis ausführen.Ich bin ein wenig besorgt über die Tatsache, dass dort steht, dass ich nur habe run=1
, scheint so zu sein run=2
, aber es ist ein Fortschritt.Aber wie übergebe ich das Ergebnis und zeige es an main an?Oder wie bringe ich es grundsätzlich zum Laufen, sodass ich einfach diese Datei ausführen und dabei alle Komponententests in diesem Verzeichnis ausführen kann?
Lösung
Sie könnten einen Testläufer verwenden, der dies für Sie tun würde. Nase ist zum Beispiel sehr gut. Beim Ausführen werden Tests im aktuellen Baum gefunden und ausgeführt.
Aktualisiert:
Hier ist ein Code aus meinen Tagen vor der Nase. Sie möchten wahrscheinlich nicht die explizite Liste der Modulnamen, aber vielleicht wird der Rest für Sie nützlich sein.
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)
Andere Tipps
Mit Python 2.7 und höher müssen Sie keinen neuen Code schreiben oder dazu keinen Drittanbieter verwenden. Eine rekursive Testausführung über die Befehlszeile ist integriert.
python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'
Sie können mehr in der lesen Python 2.7oder Python 3.x unittestes Dokumentation.
Dies ist jetzt direkt von Unittest möglich: 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)
In Python 3, wenn Sie verwenden unittest.TestCase
:
- Sie müssen einen leeren haben (oder auf andere Weise)
__init__.py
Datei in Ihremtest
Verzeichnis (muss benannt werdentest/
) - Ihre Testdateien im Inneren
test/
passen Sie das Muster antest_*.py
. Sie können sich in einem Unterverzeichnis unterhaltentest/
, und diese Subdirs können als alles andere benannt werden.
Dann können Sie alle Tests mit:
python -m unittest
Fertig! Eine Lösung weniger als 100 Linien. Hoffentlich spart ein anderer Python -Anfänger Zeit, indem er dies findet.
Nun, indem Sie den obigen Code ein wenig untersuchen (speziell verwenden TextTestRunner
und defaultTestLoader
), Ich konnte mich ziemlich nahe kommen. Schließlich habe ich meinen Code behoben, indem ich auch alle Testsuiten an einen einzelnen Suitenkonstruktor übergeben habe, anstatt sie "manuell" hinzuzufügen, was meine anderen Probleme behoben hat. Also hier ist meine Lösung.
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)
Ja, es ist wahrscheinlich einfacher, nur die Nase zu verwenden, als dies zu tun, aber das ist nicht der Punkt.
Wenn Sie alle Tests aus verschiedenen Testfallklassen ausführen möchten und Sie gerne explizit angeben möchten, können Sie dies so tun:
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)
wo uclid
ist mein Projekt und TestSymbols
und TestPatterns
sind Unterklassen von TestCase
.
Ich habe das benutzt discover
Methode und eine Überlastung von load_tests
Um dieses Ergebnis zu einem (minimalen) Zahlenzahlen von Code zu erreichen:
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()
Ausführung von Fünfen so etwas wie
Ran 27 tests in 0.187s
OK
Ich habe verschiedene Ansätze ausprobiert, aber alle scheinen fehlerhaft zu sein, oder ich muss einen Code machen, das ist ärgerlich. Aber unter Linux gibt es einen überzeugenden Weg, das nur jeden Test durch ein bestimmtes Muster zu finden und sie dann einzeln aufzurufen.
find . -name 'Test*py' -exec python '{}' \;
Und vor allem funktioniert es definitiv.
Im Falle von a verpackt Bibliothek oder Anwendung möchten Sie es nicht tun. setuptools
Werde es für dich tun.
Um diesen Befehl zu verwenden, müssen die Tests Ihres Projekts in a eingewickelt werden
unittest
Testsuite entweder durch eine Funktion, eine Testpase -Klasse oder -Methode oder ein Modul oder ein Paket, das enthältTestCase
Klassen. Wenn die benannte Suite ein Modul ist und das Modul eine hatadditional_tests()
Funktion, es wird aufgerufen und das Ergebnis (das muss a sein müssenunittest.TestSuite
) wird zu den zu durchgeführten Tests hinzugefügt. Wenn die benannte Suite ein Paket ist, Alle Submodule und Unterpackungen werden rekursiv zur gesamten Testsuite hinzugefügt.
Sagen Sie es einfach, wo sich Ihr Root -Testpaket befindet, wie:
setup(
# ...
test_suite = 'somepkg.test'
)
Und Renn python setup.py test
.
Die Datei-basierte Erkennung kann in Python 3 problematisch sein, es sei denn, Sie vermeiden relative Importe in Ihrer Testsuite, weil discover
Verwendet den Dateiimport. Obwohl es optional unterstützt wird top_level_dir
, aber ich hatte einige unendliche Rekursionsfehler. Eine einfache Lösung für einen nicht verpackten Code besteht also darin, Folgendes einzulegen __init__.py
Ihres Testpakets (siehe load_tests Protokoll).
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
Ich benutze Pydev/Liclipse und habe nicht wirklich herausgefunden, wie man alle Tests gleichzeitig von der GUI ausführt. (Bearbeiten: Klicken Sie mit der rechten Maustaste auf den Ordner root test und wählen Run as -> Python unit-test
Dies ist meine derzeitige Problemumgehung:
import unittest
def load_tests(loader, tests, pattern):
return loader.discover('.')
if __name__ == '__main__':
unittest.main()
Ich habe diesen Code in ein Modul genannt all
in meinem Testverzeichnis. Wenn ich dieses Modul als unittestes aus Liclipse ausführe, werden alle Tests durchgeführt. Wenn ich bitte nur bestimmte oder fehlgeschlagene Tests wiederholen möchte, werden nur diese Tests durchgeführt. Es stört auch meinen Befehlszeilen -Testläufer (NoSetests) nicht - es wird ignoriert.
Möglicherweise müssen Sie die Argumente ändern discover
Basierend auf Ihrem Projekt -Setup.
Basierend auf der Antwort von Stephen Cagle Ich habe Unterstützung für verschachtelte Testmodule hinzugefügt.
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)
Der Code sucht alle Unterverzeichnisse von .
zum *Tests.py
Dateien, die dann geladen werden. Es erwartet von jedem *Tests.py
eine einzelne Klasse enthalten *Tests(unittest.TestCase)
was nacheinander geladen und ausgeführt wird.
Dies funktioniert mit willkürlicher Tiefenschachtelung von Verzeichnissen/Modulen, aber jedes dazwischen liegende Verzeichnis muss eine leere enthalten __init__.py
mindestens Datei. Dadurch kann der Test die verschachtelten Module laden, indem Schrägstriche (oder Backslashes) durch Punkte ersetzt werden (siehe replace_slash_by_dot
).
Da die Testentdeckung ein vollständiges Thema zu sein scheint, gibt es ein spezielles Rahmen für die Testen der Entdeckung:
Mehr Lesen hier: https://wiki.python.org/moin/pythontestingtoolstaxonomy
Dieses BASH-Skript führt das Python-Unittest-Testverzeichnis von ÜBERALL im Dateisystem aus, unabhängig davon, in welchem Arbeitsverzeichnis Sie sich befinden:Sein Arbeitsverzeichnis ist immer dort, wo es ist test
Verzeichnis befindet.
ALLE TESTS, unabhängige $PWD
Das Unittest-Python-Modul reagiert auf Ihr aktuelles Verzeichnis, es sei denn, Sie teilen ihm mit, wo es sich befindet (mit discover -s
Möglichkeit).
Dies ist nützlich, wenn Sie sich im Haus aufhalten ./src
oder ./example
Arbeitsverzeichnis und Sie benötigen einen kurzen allgemeinen Unit-Test:
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"
python -m unittest discover -s "$readlink"/test -v
AUSGEWÄHLTE TESTS, unabhängige $PWD
Ich nenne diese Dienstprogrammdatei: runone.py
und benutze es so:
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)
Keine Notwendigkeit für eine test/__init__.py
Datei, um Ihren Paket-/Speicheraufwand während der Produktion zu belasten.
Hier ist mein Ansatz durch Erstellen eine Verpackung Tests aus der Befehlszeile ausführen:
#!/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())
Um der Einfachheit zu willen, entschuldigen Sie bitte mein Nicht-NichtsPep8 Codierungsstandards.
Anschließend können Sie die basierteste Klasse für gemeinsame Komponenten für alle Ihre Tests erstellen, sodass jeder Ihrer Test einfach aussieht:
from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
def test_foo(self):
driver = self.driver
driver.get(self.base_url + "/")
Zum Ausführen geben Sie einfach Tests als Teil der Befehlszeilenargumente an, z. B.:
./run_tests.py -h http://example.com/ tests/**/*.py