2009-03-13 4 views
51

Ich entwickle ein Python-Modul mit mehreren Quelldateien, jede mit einer eigenen Testklasse, abgeleitet von unittest direkt in der Quelle. Betrachten Sie die Verzeichnisstruktur:Python: Wie wird unittest.main() für alle Quelldateien in einem Unterverzeichnis ausgeführt?

dirFoo\ 
    test.py 
    dirBar\ 
     __init__.py 
     Foo.py 
     Bar.py 

Um zu testen, entweder Foo.py oder Bar.py möchte ich hinzufügen, das am Ende der Foo.py und Bar.py Quelldateien:

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

Und laufe Python auf jeder Quelle, dh

$ python Foo.py 
........... 
---------------------------------------------------------------------- 
Ran 11 tests in 2.314s 

OK 

Idealerweise würde ich „test.py“ automagically dirBar für alle Unittest abgeleiteten Klassen suchen und einen Aufruf von „unittest.main()“ machen. Was ist der beste Weg, dies in der Praxis zu tun?

ich mit Python versucht execfile für jeden anrufen * .py Datei in dirBar, die zum ersten .py Datei einmal ausgeführt wird gefunden & die Berufung test.py verlässt, und dann muss ich meinen Code duplizieren Unittest hinzufügen. main() in jeder Quelldatei - was gegen die DRY-Prinzipien verstößt.

Antwort

25

Ich wusste, dass es eine offensichtliche Lösung war:

dirFoo\ 
    __init__.py 
    test.py 
    dirBar\ 
     Foo.py 
     Bar.py 

Inhalt dirFoo/test.py

from dirBar import * 
import unittest 

if __name__ == "__main__": 

    unittest.main() 

die Tests ausgeführt:

$ python test.py 
........... 
---------------------------------------------------------------------- 
Ran 11 tests in 2.305s 

OK 

Sorry für die dumme Frage.

+4

Entweder haben Sie verpasst ‚dirBar/__ init __. Py' oder' von dirBar import * 'wird nicht funktionieren. Btw, verwenden Sie Kleinbuchstaben für Paket/Modulnamen. – jfs

+1

Außerdem besteht das Problem darin, dass Sie davon ausgehen, dass die Testfälle von Foo.py und Bar.py im Modul dirBar __init__.py verfügbar sind. Wenn dies nicht der Fall ist, wird test.py nichts testen. – cdleary

+4

funktioniert nicht so weit, bitte aktualisiere es –

17

Sie sollten versuchen nose. Es ist eine Bibliothek, um Tests zu erstellen und es integriert unittest oder doctest. Alles, was Sie tun müssen, ist nosetests und es wird alle Ihre Untestests für Sie finden.

% nosetests # finds all tests in all subdirectories 
% nosetests tests/ # find all tests in the tests directory 
+3

Die Frage, die explizit nach Lösungen mit Unitest gefragt wird –

+0

Es kann von Vorteil sein, irgendwelche unmittelbaren Vorteile der Verwendung von nosetests zu erklären, die mit der Absicht der Fragen in Verbindung stehen könnten? Zumal diese Frage spezifisch nach Unittest fragt. –

2

Ich kam mit einem Schnipsel, der tun kann, was Sie wollen. Es durchläuft einen Pfad, den Sie bereitstellen, und sucht nach Python-Paketen/Modulen und sammelt eine Reihe von Testsuiten aus diesen Modulen, die dann auf einmal ausgeführt werden.

Das schöne daran ist, dass es auf allen Paketen funktioniert, die unter dem angegebenen Verzeichnis verschachtelt sind, und Sie die Importe nicht manuell ändern müssen, wenn Sie neue Komponenten hinzufügen.

import logging 
import os 
import unittest 

MODULE_EXTENSIONS = set('.py .pyc .pyo'.split()) 

def unit_test_extractor(tup, path, filenames): 
    """Pull ``unittest.TestSuite``s from modules in path 
    if the path represents a valid Python package. Accumulate 
    results in `tup[1]`. 
    """ 
    package_path, suites = tup 
    logging.debug('Path: %s', path) 
    logging.debug('Filenames: %s', filenames) 
    relpath = os.path.relpath(path, package_path) 
    relpath_pieces = relpath.split(os.sep) 

    if relpath_pieces[0] == '.': # Base directory. 
     relpath_pieces.pop(0) # Otherwise, screws up module name. 
    elif not any(os.path.exists(os.path.join(path, '__init__' + ext)) 
      for ext in MODULE_EXTENSIONS): 
     return # Not a package directory and not the base directory, reject. 

    logging.info('Base: %s', '.'.join(relpath_pieces)) 
    for filename in filenames: 
     base, ext = os.path.splitext(filename) 
     if ext not in MODULE_EXTENSIONS: # Not a Python module. 
      continue 
     logging.info('Module: %s', base) 
     module_name = '.'.join(relpath_pieces + [base]) 
     logging.info('Importing from %s', module_name) 
     module = __import__(module_name) 
     module_suites = unittest.defaultTestLoader.loadTestsFromModule(module) 
     logging.info('Got suites: %s', module_suites) 
     suites += module_suites 

def get_test_suites(path): 
    """:return: Iterable of suites for the packages/modules 
    present under :param:`path`. 
    """ 
    logging.info('Base path: %s', package_path) 
    suites = [] 
    os.path.walk(package_path, unit_test_extractor, (package_path, suites)) 
    logging.info('Got suites: %s', suites) 
    return suites 

if __name__ == '__main__': 
    logging.basicConfig(level=logging.WARN) 
    package_path = os.path.dirname(os.path.abspath(__file__)) 
    suites = get_test_suites(package_path) 
    for suite in suites: 
     unittest.TextTestRunner(verbosity=2).run(suite) 
+0

Ich denke, dass Ihr 'get_test_suites (path)' in der Tat 'get_test_suites (package_path)' sein muss. –

55

Ab Python 2.7 ist die Testentdeckung im Unittest-Paket automatisiert. Von der docs:

Unittest unterstützt einfache Testentdeckung. Um kompatibel zu mit Test-Discovery zu sein, müssen alle Testdateien Module oder Pakete aus dem Top-Level-Verzeichnis des Projekts importiert werden (das bedeutet , dass ihre Dateinamen gültige Bezeichner sein müssen).

Die Testerkennung ist in TestLoader.discover() implementiert, kann aber auch über die Befehlszeile verwendet werden.Die grundlegenden Befehlszeilen Nutzung ist:

cd project_directory 
python -m unittest discover 

standardmäßig es test*.py Namen für Pakete sieht, aber das kann geändert werden, so könnten Sie so etwas wie

python -m unittest discover --pattern=*.py 

Anstelle Ihrem Test verwenden. py-Skript.

+0

ist Muster die einzige Möglichkeit, eine eingeschränkte Testgruppe zu wählen, um zu laufen? Ich habe Probleme mit dem Test, der mit der Erkennung funktioniert, aber fehlgeschlagen, wenn ich aufgrund von Pfadproblemen einzelne Tests ausführen möchte. –

+2

irgendein Vorschlag für Python 2.6? –

+0

@larrycai entweder die anderen Antworten hier sehen, oder auschecken unittest2 http://pypi.python.org/pypi/unittest2 –

26

Hier ist mein Test-Erkennungscode, der den Job zu erledigen scheint. Ich wollte sicherstellen, dass ich die Tests problemlos erweitern kann, ohne sie in einer der beteiligten Dateien aufführen zu müssen, aber auch alle Tests in eine einzige Übertest-Datei schreiben zu müssen.

So ist die Struktur

myTests.py 
testDir\ 
    __init__.py 
    testA.py 
    testB.py 

myTest.py wie folgt aussehen:

import unittest 

if __name__ == '__main__': 
    testsuite = unittest.TestLoader().discover('.') 
    unittest.TextTestRunner(verbosity=1).run(testsuite) 

Ich glaube, dies ist die einfachste Lösung für die Erstellung mehrerer Testfälle in einem Verzeichnis ist. Die Lösung erfordert Python 2.7 oder Python 3.

1

Für den Fall, dass es jemandem hilft, hier ist der Ansatz, den ich zur Lösung dieses Problems kam. Ich hatte den Anwendungsfall, wo ich die folgende Verzeichnisstruktur haben:

mypackage/ 
    tests/ 
     test_category_1/ 
      tests_1a.py 
      tests_1b.py 
      ... 
     test_category_2/ 
      tests_2a.py 
      tests_2b.py 
      ... 
     ... 

und ich möchte alle folgenden in der offensichtlichen Weise zu arbeiten und in der Lage sein, die gleichen Kommandozeilen-Argumente geliefert werden, wie durch Unittest akzeptiert:

python -m mypackage.tests 
python -m mypackage.tests.test_category_1 
python -m mypackage.tests.test_category_1.tests_1a 

Die Lösung war mypackage/tests/__init__.py wie dies einzurichten:

import unittest 

def prepare_load_tests_function (the__path__): 
    test_suite = unittest.TestLoader().discover(the__path__[0]) 
    def load_tests (_a, _b, _c): 
     return test_suite 
    return load_tests 

und mypackage/tests/__main__.py wie dies einzurichten:

import unittest 
from . import prepare_load_tests_function, __path__ 

load_tests = prepare_load_tests_function(__path__) 
unittest.main() 

und kopieren und eine leere __init__.py und die folgende __main__.py in jedem mypackage/tests/test_category_n/ einfügen:

import unittest 
from .. import prepare_load_tests_function 
from . import __path__ 

load_tests = prepare_load_tests_function(__path__) 
unittest.main() 

und auch die Standard-if __name__ == '__main__': unittest.main() in jedem eigentlichen Tests Datei hinzuzufügen.

(Funktioniert bei mir auf Python 3.3 auf Windows, ymmv.)

+0

+1 für die Verwendung der discover-API direkt, die Situationen unterstützt, in denen wir keine Kontrolle über den Python-Cmd haben – deepelement

Verwandte Themen