2016-06-20 12 views
4

Ich bin auf ein Problem gestoßen, von dem ich denke, dass es ein Fehler in den Bibliotheken ist, die ich verwende. Ich bin jedoch ziemlich neu in Python, Unittest und unittest.mock Bibliotheken, so dass dies nur ein Loch in meinem Verständnis sein kann.Warum schlägt unittest.mock fehl, wenn der Konstruktor der Produktionsklasse zusätzliche Argumente annimmt?

Während Tests bis zu einem gewissen Produktionscode hinzugefügt ich in einen Fehler laufen, ich habe eine minimale Probe erzeugt, die das Problem reproduziert:

import unittest 
import mock 

class noCtorArg: 
    def __init__(self): 
     pass 
    def okFunc(self): 
     raise NotImplemented 


class withCtorArg: 
    def __init__(self,obj): 
     pass 
    def notOkFunc(self): 
     raise NotImplemented 
    def okWithArgFunc(self, anArgForMe): 
     raise NotImplemented 

class BasicTestSuite(unittest.TestCase): 
    """Basic test Cases.""" 

    # passes 
    def test_noCtorArg_okFunc(self): 
     mockSUT = mock.MagicMock(spec=noCtorArg) 
     mockSUT.okFunc() 
     mockSUT.assert_has_calls([mock.call.okFunc()]) 

    # passes 
    def test_withCtorArg_okWithArgFuncTest(self): 
     mockSUT = mock.MagicMock(spec=withCtorArg) 
     mockSUT.okWithArgFunc("testing") 
     mockSUT.assert_has_calls([mock.call.okWithArgFunc("testing")]) 

    # fails 
    def test_withCtorArg_doNotOkFuncTest(self): 
     mockSUT = mock.MagicMock(spec=withCtorArg) 
     mockSUT.notOkFunc() 
     mockSUT.assert_has_calls([mock.call.notOkFunc()]) 


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

Wie ich die Tests ausführen und die Ausgabe ist wie folgt:

E:\work>python -m unittest testCopyFuncWithMock 
.F. 
====================================================================== 
FAIL: test_withCtorArg_doNotOkFuncTest (testCopyFuncWithMock.BasicTestSuite) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "testCopyFuncWithMock.py", line 38, in test_withCtorArg_doNotOkFuncTest 
    mockSUT.assert_has_calls([mock.call.notOkFunc()]) 
    File "C:\Python27\lib\site-packages\mock\mock.py", line 969, in assert_has_calls 
    ), cause) 
    File "C:\Python27\lib\site-packages\six.py", line 718, in raise_from 
    raise value 
AssertionError: Calls not found. 
Expected: [call.notOkFunc()] 
Actual: [call.notOkFunc()] 

---------------------------------------------------------------------- 
Ran 3 tests in 0.004s 

FAILED (failures=1) 

Ich benutze Python 2.7.11, mit Mock-Version 2.0.0 über Pip installiert.

Irgendwelche Vorschläge für was ich falsch mache? Oder sieht das wie ein Fehler in der Bibliothek aus?

+0

Nicht zu sicher über Ihr besonderes Problem, sondern nur eine Python Codierung note - für python2.7 Klassen, können Sie immer von 'erben wollen Objekt ', dh' Klasse WithCtorArg (Objekt): '. Klassennamen beginnen in der Regel mit einem Großbuchstaben (obwohl mir klar ist, dass sie in der Übersetzung in Ihren MCVE verloren gegangen sind). – dwanderson

+0

danke @dwanderson. –

Antwort

3

Interessanterweise hat die Art, wie Sie die Assert ausführen, Ihr Problem maskiert.

Versuchen, statt dies:

mockSUT.assert_has_calls(calls=[mock.call.notOkFunc()]) 

dies zu tun:

mockSUT.assert_has_calls(calls=[mock.call.notOkFunc()], any_order=True) 

Sie finden die aktuelle Ausnahme sehen:

TypeError("'obj' parameter lacking default value") 

Dies liegt daran, Sie zu instanziieren versucht eine Instanz der Klasse withCtorArg, die den Parameter obj ohne Defau hat lt Wert. Wenn Sie versucht hatte, es tatsächlich direkt zu instanziiert, würde haben Sie gesehen:

TypeError: __init__() takes exactly 2 arguments (1 given) 

Aber da man die mock Bibliothek handhaben die Instanziierung eines Mock-Objekt lassen, geschieht der Fehler dort - und Sie erhalten die TypeError Ausnahme .

Ändern der jeweiligen Klasse:

class withCtorArg: 
    def __init__(self, obj = None): 
     pass 
    def notOkFunc(self): 
     pass 
    def okWithArgFunc(self, anArgForMe): 
     pass 

und das Hinzufügen einer Standard Keine Wert für obj löst das Problem.

+0

Ja, guter Punkt. Ich hätte gehofft, dass man mit einem Mock den eigentlichen Konstruktor nicht aufrufen müsste, da es für das SUT irrelevant ist. Leider ist in einem Beispiel aus der realen Welt (zumindest in meinem Fall) das Modifizieren der Klasse, die verspottet wird, keine gute Idee, die Konstruktorargumente gibt es aus einem Grund und sind nicht optional, irgendwelche anderen Vorschläge? –

2

Ich glaube nicht, dass ich definitiv erklären kann, warum das der Fall ist, ich vermute immer noch einen Fehler in der Mock-Bibliothek, da das Problem nur für einen Testfall ohne Argumente für die aufgerufene Funktion auftritt. Danke an advance512 für den Hinweis, dass der wahre Fehler versteckt war!

jedoch, um dieses Problem zu umgehen, ohne den Produktionscode zu modifizieren ich den folgenden Ansatz verwenden werde:

# passes 
@mock.patch ('mymodule.noCtorArg') 
def test_noCtorArg_okFunc(self, noCtorArgMock): 
    mockSUT = noCtorArg.return_value 
    mockSUT.okFunc() 
    mockSUT.assert_has_calls([mock.call.okFunc()]) 

# passes 
@mock.patch ('mymodule.withCtorArg') 
def test_withCtorArg_okWithArgFuncTest(self, withCtorArgMock): 
    mockSUT = withCtorArg.return_value 
    mockSUT.okWithArgFunc("testing") 
    mockSUT.assert_has_calls([mock.call.okWithArgFunc("testing")]) 

# now passes 
@mock.patch ('mymodule.withCtorArg') 
def test_withCtorArg_doNotOkFuncTest(self, withCtorArgMock): 
    mockSUT = withCtorArg.return_value 
    mockSUT.notOkFunc() 
    mockSUT.assert_has_calls([mock.call.notOkFunc()], any_order=True) 

Edit:

Ein Problem dabei ist, dass der Mock hat nicht spec eingestellt. Dies bedeutet, dass das SUT Methoden aufrufen kann, die in der ursprünglichen Klassendefinition nicht vorhanden sind.

Ein alternativer Ansatz ist die Klasse wickeln verspottet werden:

class withCtorArg: 
    def __init__(self,obj): 
     pass 
    def notOkFunc(self): 
     raise NotImplemented 
    def okWithArgFunc(self, anArgForMe): 
     raise NotImplemented 

class wrapped_withCtorArg(withCtorArg): 
    def __init__(self): 
     super(None) 

class BasicTestSuite(unittest.TestCase): 
    """Basic test Cases.""" 

    # now passes 
    def test_withCtorArg_doNotOkFuncTest(self): 
     mockSUT = mock.MagicMock(spec=wrapped_withCtorArg) 
     mockSUT.notOkFunc() 
     #mockSUT.doesntExist() #causes the test to fail. "Mock object has no attribute 'doesntExist'" 
     assert isinstance (mockSUT, withCtorArg) 
     mockSUT.assert_has_calls([mock.call.notOkFunc()], any_order=True) 
+0

Der Versuch, eine Instanz einer Klasse zu instanziieren, die Parameter in ihrem Konstruktor benötigt, ohne Argumente anzugeben, würde fehlschlagen, wenn Sie versucht hätten, dies direkt zu tun. Warum erwartest du, dass es in einem Mock funktioniert, besonders ein MagicMock, der automatisch verschiedene Methoden "umhüllt" und die von dir getätigten Aufrufe überwacht und ob sie gültig sind oder nicht? :) Ich würde sagen, dass das Verhalten, das Sie gesehen haben, sinnvoll ist, außer für den Fehler in der Bibliothek. – advance512

+0

Ich hoffte, dass die Bibliothek den Konstruktor umschließen würde (wie ich in dieser Antwort getan habe), da wir keine Instanz der Klasse _really_ wollen. Wir wollen nur einen vorgeben, der eigentlich nichts macht, außer die getätigten Anrufe auszuspionieren und falsche Rückgabewerte zu erzeugen. In diesem Fall ist es nicht wirklich wichtig, dass die Konstruktorsignatur übereinstimmt, da sie außerhalb des SUT erstellt und injiziert wird. –

+0

Alternativ wäre es in Ordnung, wenn es beim Erstellen eines Mocks die Möglichkeit gibt, die Konstruktorparameter anzugeben, damit sie an den Klassenkonstruktor weitergeleitet werden können. Weißt du, wie man das mit dieser Bibliothek macht? Ich habe in der Dokumentation nichts über das Erstellen von Mocks für Klassen mit Konstruktorargumenten gefunden. Danke für Ihr Interesse übrigens. –