2008-10-07 7 views
12

Ich bin in einem Projekt, wo wir beginnen, einige massive Code-Basis zu refactoring. Ein Problem, das sofort aufkam, ist, dass jede Datei viele andere Dateien importiert. Wie kann ich das in meinem Komponententest auf elegante Art und Weise vortäuschen, ohne den eigentlichen Code ändern zu müssen, damit ich mit Komponententests beginnen kann?Python, Unit-Testing und Mocking Importe

Als ein Beispiel: Die Datei mit den Funktionen, die ich testen möchte, importiert zehn andere Dateien, die Teil unserer Software und nicht Python Core-Bibliotheken sind.

Ich möchte in der Lage sein, die Komponententests so separat wie möglich zu starten, und jetzt werde ich nur Funktionen testen, die nicht von den Dateien abhängen, die importiert werden.

Danke für alle Antworten.

Ich wusste nicht wirklich, was ich von Anfang an machen wollte, aber jetzt denke ich, dass ich weiß.

Problem war, dass einige Importe nur möglich war, wenn die gesamte Anwendung aufgrund einiger Auto-Magie von Drittanbietern ausgeführt wurde. Also musste ich einige Stubs für diese Module in einem Verzeichnis erstellen, auf das ich mit sys.path hingewiesen habe

Jetzt kann ich die Datei importieren, die die Funktionen enthält, für die ich Tests in meiner Unit-Test-Datei schreiben möchte, ohne sich zu beschweren fehlende Module.

Antwort

7

Wenn Sie ein Modul importieren und gleichzeitig sicherstellen möchten, dass es nichts importiert, können Sie die integrierte Funktion __import__ ersetzen.

Zum Beispiel verwenden diese Klasse:

class ImportWrapper(object): 
    def __init__(self, real_import): 
     self.real_import = real_import 

    def wrapper(self, wantedModules): 
     def inner(moduleName, *args, **kwargs): 
      if moduleName in wantedModules: 
       print "IMPORTING MODULE", moduleName 
       self.real_import(*args, **kwargs) 
      else: 
       print "NOT IMPORTING MODULE", moduleName 
     return inner 

    def mock_import(self, moduleName, wantedModules): 
     __builtins__.__import__ = self.wrapper(wantedModules) 
     try: 
      __import__(moduleName, globals(), locals(), [], -1) 
     finally: 
      __builtins__.__import__ = self.real_import 

Und in Ihrem Code-Test statt import myModule zu schreiben, schreiben:

wrapper = ImportWrapper(__import__) 
wrapper.mock_import('myModule', []) 

Das zweite Argument für mock_import ist eine Liste der Modulnamen Sie do möchte im inneren Modul importieren.

Dieses Beispiel kann weiter modifiziert werden z.B.Importieren Sie ein anderes Modul als gewünscht, anstatt es einfach nicht zu importieren oder das Modulobjekt mit einem eigenen benutzerdefinierten Objekt zu verspotten.

+0

Sie möchten 'try: finally:' in der Methode 'mock_import' hinzufügen, um zu vermeiden, dass das System den Wrapped-Import anstelle des Standard-Imports bei einem Fehler erhält – Yonatan

+0

@Yonatan: Sie haben Recht, danke! Ich habe meinen Code geändert. – DzinX

1

"importiert viele andere Dateien"? Importiert viele andere Dateien, die Teil Ihrer benutzerdefinierten Codebasis sind? Oder importiert viele andere Dateien, die Teil der Python-Distribution sind? Oder importiert viele andere Open-Source-Projektdateien?

Wenn Ihre Importe nicht funktionieren, haben Sie eine "einfache" PYTHONPAT H-Problem. Holen Sie alle Ihre verschiedenen Projektverzeichnisse auf eine PYTHONPATH, die Sie zum Testen verwenden können. Wir haben einen ziemlich komplexen Pfad, in Windows verwalten wir es wie dieses

@set Part1=c:\blah\blah\blah 
@set Part2=c:\some\other\path 
@set that=g:\shared\stuff 
set PYTHONPATH=%part1%;%part2%;%that% 

Wir halten jedes Stück des Weges getrennt, so dass wir (a) wissen, wo die Dinge herkommen und (b) Änderung verwalten, wenn wir uns bewegen Dinge herum.

Da die PYTHONPATH in der Reihenfolge gesucht wird, können wir steuern, was verwendet wird, indem Sie die Reihenfolge auf dem Pfad anpassen.

Sobald Sie "alles" haben, wird es eine Frage des Vertrauens.

Entweder

  • Sie vertrauen etwas (das heißt, die Python-Code-Basis) und es einfach importieren.

Oder

  • Sie kein Vertrauen in etwas (das heißt, Ihren eigenen Code), und Sie

    1. Test separat und
    2. es für die Prüfung stand-alone verspotten.

Möchten Sie die Python-Bibliotheken testen? Wenn ja, hast du eine Menge Arbeit.Wenn nicht, sollten Sie vielleicht nur die Dinge verspotten, die Sie tatsächlich testen werden.

1

Wenn Sie wirklich mit dem Python-Import-Mechanismus herumspielen wollen, werfen Sie einen Blick auf das Modul ihooks. Es bietet Tools zum Ändern des Verhaltens der integrierten __import__. Aber es ist nicht klar aus deiner Frage, warum du das tun musst.

0

Keine schwierige Manipulation ist notwendig, wenn Sie vor den Gerätetests eine schnelle und saubere Lösung wünschen.

Wenn sich die Komponententests in der gleichen Datei wie der Code befinden, den Sie testen möchten, löschen Sie einfach das unerwünschte Modul aus dem globals() Wörterbuch.

Hier ist ein ziemlich langwieriges Beispiel: Angenommen, Sie impp.py mit Inhalt ein Modul haben:

value = 5 

Jetzt, die Sie in Ihrer Testdatei schreiben:

>>> import impp 
>>> print globals().keys() 
>>> def printVal(): 
>>>  print impp.value 
['printVal', '__builtins__', '__file__', 'impp', '__name__', '__doc__'] 

Beachten Sie, dass impp unter dem sind globals, weil es importiert wurde. Der Aufruf der Funktion printVal die impp Modul funktioniert immer noch verwendet:

>>> printVal() 
5 

Aber jetzt, wenn Sie impp Schlüssel aus globals() entfernen ...

>>> del globals()['impp'] 
>>> print globals().keys() 
['printVal', '__builtins__', '__file__', '__name__', '__doc__'] 

... und versuchen printVal() zu nennen, werden Sie Get:

>>> printVal() 
Traceback (most recent call last): 
    File "test_imp.py", line 13, in <module> 
    printVal() 
    File "test_imp.py", line 5, in printVal 
    print impp.value 
NameError: global name 'impp' is not defined 

... was wahrscheinlich genau das ist, was Sie erreichen wollen.

Um es in Ihren Komponententests zu verwenden, können Sie die globalen Variablen löschen, bevor Sie die Testsuite ausführen, z. in __main__:

if __name__ == '__main__': 
    del globals()['impp'] 
    unittest.main() 
+0

Dank für Ihre Antwort habe ich viel daraus gelernt. Aber genau das Gegenteil möchte ich tun. Die Tests befinden sich in einer anderen Datei, und wenn ich die Datei mit den zu testenden Funktionen importiere, möchte ich, dass diese Datei denkt, dass alle anderen Dateien bereits importiert sind. –

0

In Ihrem Kommentar above, Sie sagen, Sie Python überzeugen wollen, dass bestimmte Module bereits eingeführt worden sind. Dies scheint immer noch ein seltsames Ziel zu sein, aber wenn das wirklich das ist, was Sie tun möchten, können Sie im Prinzip hinter dem Mechanismus des Importsystems herumschleichen und sys.modules ändern. Ich bin mir nicht sicher, wie das für Paket-Importe funktionieren würde, aber sollte für absolute Importe gut sein.