2015-04-18 16 views
11

Ich habe ein Paket mit einem Verzeichnis „Tests“, in dem ich meine Unit-Tests zu speichern. Mein Paket wie folgt aussieht:rekursive Unittest entdecken

. 
├── LICENSE 
├── models 
│   └── __init__.py 
├── README.md 
├── requirements.txt 
├── tc.py 
├── tests 
│   ├── db 
│   │   └── test_employee.py 
│   └── test_tc.py 
└── todo.txt 

Von meinem Paketverzeichnis, ich mag sowohl tests/test_tc.py und tests/db/test_employee.py finden können. Ich würde es vorziehen, nicht eine Drittanbieter-Bibliothek (nose oder usw.) oder müssen manuell ein TestSuite bauen laufen diese in installieren muß.

Sicherlich ist es eine Möglichkeit, unittest discover zu sagen, nicht auf der Suche zu beenden, sobald es eine gefunden werden Prüfung? python -m unittest discover -s tests wird tests/test_tc.py finden und python -m unittest discover -s tests/dbtests/db/test_employee.py finden. Gibt es keinen Weg, beides zu finden?

+0

Haben Sie sich z.B. http://stackoverflow.com/q/644821/3001761? Ich denke, einige dieser Antworten könnten für Ihre Zwecke angepasst werden. – jonrsharpe

Antwort

28

Bei ein bisschen Graben scheint es, dass solange tiefere Module importiert werden können, sie über python -m unittest discover entdeckt werden. Die Lösung bestand also einfach darin, jedem Verzeichnis eine __init__.py Datei hinzuzufügen, um daraus Pakete zu machen.

. 
├── LICENSE 
├── models 
│   └── __init__.py 
├── README.md 
├── requirements.txt 
├── tc.py 
├── tests 
│   ├── db 
│   │   ├── __init__.py  # NEW 
│   │   └── test_employee.py 
│   ├── __init__.py   # NEW 
│   └── test_tc.py 
└── todo.txt 

Solange jedes Verzeichnis ein __init__.py hat, kann python -m unittest discover die entsprechenden test_* Modul importieren.

+3

Ich halte dies für die richtige Antwort. –

4

Wenn Sie eine __init__.py Datei innerhalb von Tests hinzufügen möchten, können Sie eine load_tests Funktion dort platzieren, die die Erkennung für Sie übernimmt.

Wenn ein Test Paketname (Verzeichnis mit __init__.py) das Muster dann wird das Paket für eine ‚load_tests‘ Funktion überprüft werden. Wenn diese vorhanden ist, dann wird es mit Lader, Tests, Muster bezeichnet werden.

Wenn load_tests existiert dann Entdeckung macht nicht recurse in das Paket, ist load_tests verantwortlich alle Tests im Paket zum Laden.

Ich bin weit davon entfernt davon überzeugt, dass dies der beste Weg ist, aber eine Möglichkeit, diese Funktion zu schreiben, wäre:

import os 
import pkgutil 
import inspect 
import unittest 

# Add *all* subdirectories to this module's path 
__path__ = [x[0] for x in os.walk(os.path.dirname(__file__))] 

def load_tests(loader, suite, pattern): 
    for imp, modname, _ in pkgutil.walk_packages(__path__): 
     mod = imp.find_module(modname).load_module(modname) 
     for memname, memobj in inspect.getmembers(mod): 
      if inspect.isclass(memobj): 
       if issubclass(memobj, unittest.TestCase): 
        print("Found TestCase: {}".format(memobj)) 
        for test in loader.loadTestsFromTestCase(memobj): 
         print(" Found Test: {}".format(test)) 
         suite.addTest(test) 

    print("=" * 70) 
    return suite 

ziemlich hässlich, ich bin einverstanden.

Zuerst fügen Sie alle Unterverzeichnisse auf den Pfad der Testpakete (Docs).

Dann verwenden Sie pkgutil den Weg zu gehen, für Pakete oder Module suchen.

Wenn es einen findet, überprüft es dann die Modulmitglieder, ob sie Klassen sind und ob es sich um Klassen handelt, ob es sich um Unterklassen von unittest.TestCase handelt. Wenn dies der Fall ist, werden die Tests innerhalb der Klassen in die Testsuite geladen.

So, jetzt aus Ihrem Projektstamm, können Sie

python -m unittest discover -p tests 

Mit dem -p Muster Switch-Typ. Wenn alles gut geht, werden Sie sehen, was ich sah, was so etwas wie ist:

Found TestCase: <class 'test_tc.TestCase'> 
    Found Test: testBar (test_tc.TestCase) 
    Found Test: testFoo (test_tc.TestCase) 
Found TestCase: <class 'test_employee.TestCase'> 
    Found Test: testBar (test_employee.TestCase) 
    Found Test: testFoo (test_employee.TestCase) 
====================================================================== 
.... 
---------------------------------------------------------------------- 
Ran 4 tests in 0.001s 

OK 

das, was erwartet wurde, jede meiner zwei Beispieldateien enthalten zwei Tests, testFoo und testBar jeder.

Edit: Nach etwas mehr graben, es sieht aus wie Sie diese Funktion angeben könnte als:

def load_tests(loader, suite, pattern): 
    for imp, modname, _ in pkgutil.walk_packages(__path__): 
     mod = imp.find_module(modname).load_module(modname) 
     for test in loader.loadTestsFromModule(mod): 
      print("Found Tests: {}".format(test._tests)) 
      suite.addTests(test) 

Diese die loader.loadTestsFromModule() Methode anstelle der loader.loadTestsFromTestCase() Methode verwendet ich oben verwendet.Es ändert immer noch den Paketpfad tests und geht es auf der Suche nach Modulen, die ich denke, ist der Schlüssel hier.

Die Ausgabe sieht ein bisschen anders jetzt, da wir gefunden Testsuite zu einem Zeitpunkt unsere Testsuite suite sind und fügte hinzu:

python -m unittest discover -p tests 
Found Tests: [<test_tc.TestCase testMethod=testBar>, <test_tc.TestCase testMethod=testFoo>] 
Found Tests: [<test_employee.TestCase testMethod=testBar>, <test_employee.TestCase testMethod=testFoo>] 
====================================================================== 
.... 
---------------------------------------------------------------------- 
Ran 4 tests in 0.000s 

OK 

Aber wir noch bekommen die vier Tests, die wir erwarten, in beiden Klassen, in beiden Unterverzeichnissen.

+1

Das ist massiv härter als 'python -m unittest discover -s testet --recursive', was ich wirklich hoffte, dass jemand vor dem Abstimmen snarkily kommentieren würde, um zu schließen. (das heißt, * danke! *) –

+1

@AdamSmith kein Problem, ich hoffe, es hilft - nach ein wenig Graben, scheint es eine Methode zu sein, die ein bisschen einfacher ist und sich mehr auf die Unittest-Entdeckung verlässt als die explizite Überprüfung, die ich hatte - ich bearbeite meine Antwort, um diese Alternative einzuschließen. – jedwards

+0

Hmm, es gibt einen noch einfacheren Weg, den ich zufällig entdeckt habe. Ich antworte dir selbst, obwohl deine noch narrensicherer ist. –