wake-up-neo.net

Wie führe ich alle Python Komponententests in einem Verzeichnis aus?

Ich habe ein Verzeichnis, das meine Python Unit-Tests 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 dem oben genannten Testformular ausführen und das Ergebnis zurückgeben. Ich habe bisher zwei Methoden ausprobiert, beide sind fehlgeschlagen. Ich werde die beiden Methoden zeigen, und ich hoffe 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 dann dieses unittest.main() doodad aufrufe, funktioniert es, oder?" 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 __== "__main__":
     unittest.main()

Das hat nicht funktioniert, das Ergebnis war:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Aber für meinen zweiten Versuch, ok, werde ich vielleicht versuchen, das Ganze auf eine "manuellere" Art und Weise zu testen. 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 __== "__main__":
    unittest.main()

Das hat auch nicht funktioniert, aber es scheint so nah!

$ 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 habe anscheinend eine Suite und kann das Ergebnis ausführen. Ich bin ein wenig besorgt über die Tatsache, dass es heißt, ich habe nur run=1, So scheint es, sollte es run=2 Sein, aber es ist ein Fortschritt. Aber wie übergebe ich das Ergebnis und zeige es main an? Oder wie bekomme ich es im Grunde zum Laufen, damit ich diese Datei einfach ausführen und dabei alle Komponententests in diesem Verzeichnis ausführen kann?

255
Stephen Cagle

Sie könnten einen Testläufer verwenden, der dies für Sie erledigt. Nase ist zum Beispiel sehr gut. Beim Ausführen werden Tests im aktuellen Baum gefunden und ausgeführt.

Aktualisiert:

Hier ist ein Code aus meiner Zeit vor der Nase. Sie möchten wahrscheinlich nicht die explizite Liste der Modulnamen, aber vielleicht ist der Rest für Sie nützlich.

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)
101
Ned Batchelder

Mit Python 2.7 und höher müssen Sie keinen neuen Code schreiben oder Tools von Drittanbietern verwenden, die 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 Python 2.7 oder Python 3.x unittest Dokumentation lesen.

400
Travis Bear

Dies ist jetzt direkt aus unittest möglich: nittest.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)
56
slaughter98

In python 3, wenn Sie unittest.TestCase Verwenden:

  • Sie müssen eine leere (oder anderweitig) __init__.py - Datei in Ihrem test -Verzeichnis haben (muss den Namen test/ Haben)
  • Ihre Testdateien in test/ Stimmen mit dem Muster test_*.py Überein. Sie können sich in einem Unterverzeichnis unter test/ Befinden und diese Unterverzeichnisse können beliebig benannt werden.

Dann können Sie alle Tests ausführen mit:

python -m unittest

Getan! Eine Lösung mit weniger als 100 Zeilen. Hoffentlich spart ein anderer python Anfänger Zeit, indem er dies findet.

52
tmck-code

Nun, indem ich den obigen Code ein wenig studiert habe (insbesondere mit TextTestRunner und defaultTestLoader), konnte ich ziemlich nahe kommen. Schließlich habe ich meinen Code korrigiert, indem ich auch alle Testsuiten an einen einzelnen Suites-Konstruktor übergeben habe, anstatt sie "manuell" hinzuzufügen, wodurch meine anderen Probleme behoben wurden. 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 benutzen, als dies zu tun, aber das ist nicht der Punkt.

30
Stephen Cagle

Wenn Sie alle Tests aus verschiedenen Testfallklassen ausführen und diese gerne explizit angeben möchten, können Sie dies folgendermaßen tun:

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

if __== "__main__":

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

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

wobei uclid mein Projekt ist und TestSymbols und TestPatterns Unterklassen von TestCase sind.

24

Ich habe die discover -Methode und eine Überladung von load_tests, um dieses Ergebnis in einer (meiner Meinung nach minimalen) Anzahl von Codezeilen zu erzielen:

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

Hinrichtung auf Fünfer sowas

Ran 27 tests in 0.187s
OK
13
rds

Ich habe verschiedene Ansätze ausprobiert, aber alle scheinen fehlerhaft zu sein, oder ich muss etwas Code erfinden, das ist ärgerlich. Aber es gibt unter Linux einen bequemen Weg, einfach 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.

7
zinking

Im Fall einer gepackten Bibliothek oder Anwendung möchten Sie dies nicht tun. setuptoolserledigt das für Sie .

Um diesen Befehl verwenden zu können, müssen die Tests Ihres Projekts von einer Funktion, einer TestCase-Klasse oder -Methode oder einem Modul oder Paket, das unittest -Klassen enthält, in eine TestCase -Testsuite eingeschlossen werden. Wenn die genannte Suite ein Modul ist und das Modul eine additional_tests() -Funktion hat, wird es aufgerufen und das Ergebnis (das ein unittest.TestSuite Sein muss) zu den auszuführenden Tests hinzugefügt. Wenn die genannte Suite ein Paket ist , werden alle Submodule und Subpakete rekursiv zur gesamten Testsuite hinzugefügt .

Sagen Sie ihm einfach, wo sich Ihr Root-Testpaket befindet:

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

Und führen Sie python setup.py test Aus.

Die dateibasierte Erkennung kann in Python 3) problematisch sein, es sei denn, Sie vermeiden relative Importe in Ihre Testsuite, da discover den Dateiimport verwendet Es unterstützt optionales top_level_dir, aber ich hatte einige unendliche Rekursionsfehler. Eine einfache Lösung für einen nicht gepackten Code besteht darin, Folgendes in __init__.py Ihres Testpakets einzufügen (siehe load_tests Protocol ).

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
6
saaj

Ich benutze PyDev/LiClipse und habe nicht wirklich herausgefunden, wie man alle Tests auf einmal über die GUI ausführt. (Bearbeiten: Klicke mit der rechten Maustaste auf den Stamm-Testordner und wähle Run as -> Python unit-test

Dies ist meine aktuelle Problemumgehung:

import unittest

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

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

Ich habe diesen Code in einem Modul namens all in meinem Testverzeichnis abgelegt. Wenn ich dieses Modul als unittest von LiClipse aus starte, werden alle Tests ausgeführt. Wenn ich nur bestimmte oder fehlgeschlagene Tests wiederholen möchte, werden nur diese Tests ausgeführt. Es stört auch nicht meinen Kommandozeilen-Testläufer (nosetests) - es wird ignoriert.

Möglicherweise müssen Sie die Argumente entsprechend Ihrer Projektkonfiguration in discover ändern.

4
Dunes

Basierend auf der Antwort von Stephen Cagle habe ich 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 durchsucht alle Unterverzeichnisse von . Nach *Tests.py Dateien, die dann geladen werden. Es erwartet, dass jedes *Tests.py Eine einzelne Klasse *Tests(unittest.TestCase) enthält, die nacheinander geladen und ausgeführt wird.

Dies funktioniert mit beliebig tiefen Verschachtelungen von Verzeichnissen/Modulen, aber jedes Verzeichnis dazwischen muss mindestens eine leere __init__.py - Datei enthalten. Dadurch kann der Test die verschachtelten Module laden, indem Schrägstriche (oder umgekehrte Schrägstriche) durch Punkte ersetzt werden (siehe replace_slash_by_dot).

2
Peter Kofler

Dieses BASH-Skript führt das Verzeichnis python unittest test von ANYWHERE im Dateisystem aus, unabhängig davon, in welchem ​​Arbeitsverzeichnis Sie sich befinden: Das Arbeitsverzeichnis befindet sich immer dort, wo sich das Verzeichnis test befindet gelegen.

ALLE TESTS, unabhängig von $ PWD

das Modul unittest Python ist abhängig von Ihrem aktuellen Verzeichnis, sofern Sie nicht angeben, wo (mit discover -s Möglichkeit).

Dies ist nützlich, wenn Sie im ./src oder ./example Arbeitsverzeichnis und Sie benötigen einen schnellen Gesamt-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ängig von $ PWD

Ich nenne diese Utility-Datei: 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 ein test/__init__.py Datei, um Ihren Paket-/Speicher-Overhead während der Produktion zu belasten.

1
John Greene

Da die Testerkennung ein vollständiges Thema zu sein scheint, gibt es ein spezielles Framework für die Testerkennung:

Weitere Informationen finden Sie hier: https://wiki.python.org/moin/PythonTestingToolsTaxonomy

0
Bactisme