2016-03-03 28 views
6

Ich habe eine Einstiegspunktfunktion es auf ein Objekt main nennen, die ich unmocked bleiben möchte, da es mehrere andere Methoden für das Objekt aufruft:Wie packe ich ein Objekt, so dass alle Methoden außer einem verspottet werden?

class Thing(object): 

    def main(self): 
     self.alpha() 
     self.bravo() 

    def alpha(self): 
     self.charlie() 

    def bravo(self): 
     raise TypeError("Requires Internet connection!") 

    def charlie(self): 
     raise Exception("Bad stuff happens here!") 

Das ist ziemlich geradlinig manuell zu verspotten:

thing = Thing() 
thing.alpha = MagicMock() 
thing.bravo = MagicMock() 

und ich kann sicherstellen testen, dass alpha und bravo beide einmal genannt, kann ich Nebenwirkungen in alpha und bravo auf sicherzustellen, dass sie behandelt, etc. etc.

Was ich bin besorgtes Abo ut ist, wenn sich die Code-Definition ändert und jemand einen charlie Aufruf an main hinzufügt. Es wird nicht gespottet, also werden die Nebenwirkungen spürbar (und sie sind Dinge wie das Schreiben in eine Datei, das Verbinden mit einer Datenbank, das Holen von Sachen aus dem Internet, so dass eine einfache Ausnahme mich nicht darauf aufmerksam macht, dass die Tests jetzt sind Schlecht).

Mein Plan war zu überprüfen, dass mein Mock-Objekt keine anderen Methoden aufruft als die, die ich sagen soll (oder eine Testausnahme auslösen). Allerdings, wenn ich so etwas tun:

MockThing = create_autospec(Thing) 
thing = Thing() 
thing.main() 

print thing.method_calls 
# [calls.main()] 

Dann wird main auch verspottet, so dass es keine anderen Methoden aufruft. Wie kann ich jede Methode, aber die Hauptmethode vortäuschen? (Ich möchte, dass method_calls [calls.alpha(), calls.bravo()] ist).

Edit: Für Hacking Antworten

Nun habe ich eine wirklich hacky Lösung, aber ich hoffe, es gibt eine bessere Antwort als diese. Grundsätzlich rebind ich die Methode von der ursprünglichen Klasse (Python bind an unbound method)

MockThing = create_autospec(Thing) 
thing = MockThing() 
thing.main = Thing.ingest.__get__(thing, Thing) 
thing.main() 

print thing.method_calls 
# [calls.alpha(), calls.bravo()] 

Aber es hat eine einfachere Lösung sein, als Funktion Deskriptoren!

+0

Ich bekomme Fehler "Objekt hat kein Attribut 'ingest", irgendeine Idee, was das verursacht? Ich benutze Python 2.7 –

Antwort

0

Es sieht so aus, als ob Sie die Komponententests entweder in Ihre Integrations-/Funktionstests einbeziehen oder Sie sind besorgt, andere Dinge außer einer bestimmten Einheit zu testen.

Wenn Sie Thing testen, sollten Sie sich über die Teile lustig machen, die Sie nicht testen. Aber wenn Sie testen, wie Thing integriert mit anderen Dingen (wie der DB) dann sollten Sie Ihre tatsächliche Thing testen, nicht ein verspottet man.

Auch dies ist, wo Dependency Injection sehr viel Sinn macht, weil man so etwas tun würde:

Sie in einem Mock für die dangerous_thing passieren kann
class Thing: 
    def __init__(self, db, dangerous_thing): 
     self.db = db 
     self.dangerous_thing = dangerous_thing 

    #.... 

    def charlie(self): 
     foxtrot = self.dangerous_thing.do_it() 

Jetzt, während Sie Thing auszutesten sind so Sie müssen sich nicht um wirklich kümmern machen die dangerous_thing.

+0

Vielen Dank für Ihren Rat, Wayne - aber das beantwortet meine Frage nicht. – bbengfort

3

Wann habe ich diese seltsame Art von Sachen wie eine echte Methode einer Klasse aufrufen, die ich verspotten würde ich die Methode statischen Verweis aufrufen verwendet:

mt = Mock(Thing) 
Thing.main(mt) 
print(mt.mock_calls) 

[call.alpha(), call.bravo()] 

Wie dem auch sei, nachdem schreiben Sie Ihre Test es ist besser zu trennen Verwenden Sie einige Mitarbeiter, um zu trennen, was Sie vortäuschen sollten, was Sie testen möchten: Verwenden Sie diese Tests, um den Refactor des Produktionscodes zu führen und schließlich Ihren Test zu refaktorisieren, um diese Art von schmutzigen Tests zu entfernen.

+0

Der Aufruf der statischen Version der Methode hat mir hier geholfen. Vielen Dank! –

1

Ich hatte das gleiche Problem, aber ich habe einen Weg gefunden, es zu tun, mit dem ich glücklich bin. Das folgende Beispiel verwendet Ihre Thing Klasse wie oben aufgeführt:

import mock 

mock_thing = mock.create_autospec(Thing) 
mock_thing.main = lambda x: Thing.main(mock_thing, x) 

wird in mock_thing eine tatsächliche ‚main‘ Funktion aufrufen, die zu mock_thing Dieses Ergebnis, aber mock_thing.alpha() und mock_thing.beta() werden beide genannt werden als Mock! (Ersetzen Sie x durch alle Parameter, die Sie an die Funktion übergeben).

Hoffentlich funktioniert das für Sie!