2013-01-22 2 views
5

Ich verarbeite eine Sequenz von benutzerdefinierten Objekten. Es sieht ähnlich dem folgenden:Wie kann ich Aufrufe bestätigen, die Sequenzargumente mit Python Mock akzeptieren?

class Thing(object): 
    def __init__(self, x, y): 
     self.x = x 
     self.y = y 

Die Methode, die ich zur Zeit testen bin hat eine ähnliche Funktionalität wie folgt zusammen:

def my_function(things): 
    x_calc = calculate_something(t.x for t in things) 
    y_calc = calculate_something(t.y for t in things) 
    return x_calc/y_calc 

Das Problem, das ich bin vor, um die Anrufe zu calculate_something testen. Ich möchte behaupten, dass diese Anrufe geschehen, so etwas wie so:

calculateSomethingMock.assert_any_call(the_sequence) 

ich nicht über die Reihenfolge der Sequenz in calculate_something vergangen ist es egal, aber ist mir egal, dass die Elemente alle vorhanden sind. Ich könnte die Generator-Funktion in einen Anruf zu set wickeln, aber ich fühle mich nicht wie mein Test sollte diktieren, welche Art von Sequenz in calculate_something übergeben wird. Ich sollte in der Lage sein, irgendeine Art von Sequenz zu übergeben. Ich könnte alternativ eine Methode erstellen, die die Sequenz generiert, anstatt Generatorsyntax zu verwenden und diese Methode vorzutäuschen, aber das scheint wie Overkill.

Wie kann ich diese Behauptung am besten strukturieren, oder ist mein Problem hier ein Hinweis auf schlecht strukturierten Code?

Ich benutze Python 2.7.3 mit Mock 1.0.1.

(Für alle, die gezwungen fühlt, sich dazu zu äußern, ich bin mir bewusst, ich Test letzte tue, und dass dies nicht die größte Praxis betrachtet wird.)

Edit:

Nach Uhr this marvelous talk entitled "Why You Don't Get Mock Objects by Gregory Moeck" Ich habe darüber nachgedacht, ob ich mich überhaupt über die Methode calculate_something lustig machen sollte.

Antwort

2

Ich habe den Code, mit dem ich ursprünglich gearbeitet habe, seit einiger Zeit nicht mehr berührt, aber ich habe meine Herangehensweise an das Testen im Allgemeinen überdenken müssen.Ich habe versucht, vorsichtiger zu sein bei dem, was ich tue, und ich mache nichts. Ich habe kürzlich festgestellt, dass ich unbewusst anfing, dieser Faustregel zu folgen: etwas vorzutäuschen, wenn es meinen Test kürzer und einfacher macht und es in Ruhe lässt, wenn es den Test komplizierter macht. Einfache Eingabe-/Ausgabetests reichen bei dieser Methode aus. Es gibt keine externen Abhängigkeiten wie eine Datenbank oder Dateien. Kurz gesagt, denke ich, die Antwort auf meine Frage lautet: "Ich sollte nicht calculate_something spotten." Dadurch wird mein Test schwieriger zu lesen und zu warten.

7

Mit Blick auf die Mock-Dokumentation gibt es eine call_args_list, die tun wird, was Sie wollen.

So werden Sie calculate_something auf Ihrem Test Mock out.

calculate_something = Mock(return_value=None) 

Nachdem Sie my_function beenden Sie die Argumente, indem Sie übergeben überprüfen:

calculate_something.call_args_list 

, das eine Liste aller Anrufe zu ihm zurückkehren wird (mit den entsprechenden Elementen bestanden).

bearbeiten:

(Leider dauerte es so lange, ich hatte Python3.3 auf meinem Rechner installieren)

mymodule.py

class Thing: 
    ... 
def calculate_something: 
    ... 

def my_function(things): 
    # Create the list outside in order to avoid a generator object 
    # from being passed to the Mock object. 

    xs = [t.x for t in things] 
    x_calc = calculate_something(xs) 

    ys = [t.y for t in things] 
    y_calc = calculate_something(ys) 
    return True 

test_file. py

import unittest 
from unittest.mock import patch, call 
import mymodule 



class TestFoo(unittest.TestCase): 

    # You can patch calculate_something here or 
    # do so inside the test body with 
    # mymodule.calcualte_something = Mock() 
    @patch('mymodule.calculate_something') 
    def test_mock(self, mock_calculate): 

     things = [mymodule.Thing(3, 4), mymodule.Thing(7, 8)] 

     mymodule.my_function(things) 

     # call_args_list returns [call([3, 7]), call([4, 8])] 
     callresult = mock_calculate.call_args_list 


     # Create our own call() objects to compare against 
     xargs = call([3, 7]) 
     yargs = call([4, 8]) 

     self.assertEqual(callresult, [xargs, yargs]) 

     # or 
     # we can extract the call() info 
     # http://www.voidspace.org.uk/python/mock/helpers.html#mock.call.call_list 
     xargs, _ = callresult[0] 
     yargs, _ = callresult[1] 

     xexpected = [3, 7] 
     yexpected = [4, 8] 

     self.assertEqual(xargs[0], xexpected) 
     self.assertEqual(yargs[0], yexpected) 

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

Ich versuche zu vermeiden, in diese Liste zu graben, aber ich nehme an, es könnte der einzige Weg sein. Können Sie ein Beispiel geben? – jpmc26

+0

Danke. Dieser Test beruht auf der Methodenerhaltungsreihenfolge und beruht auch auf der Methode, die eine Liste übergibt. Ich bin nicht sehr scharf auf den Test, der auf Ordnung beruht. Wenn ich mich entscheide, zu einer anderen Datenstruktur für das in 'calculate_something' übergebene Argument zu wechseln, soll der Test fehlschlagen? Oder umgekehrt, wenn ich den Test ändere, um den Aufruf für einen anderen Datentyp zu überprüfen, möchte ich die Methode ändern müssen, um es erneut zu übergeben? – jpmc26

+0

Ja, aber denken Sie daran, dies ist ein Beispiel. Sie können 'calcome_something' jede andere Datenstruktur übergeben, aktualisieren Sie einfach den Test, um das widerzuspiegeln (durch Ändern der erwarteten Werte). Sie möchten, dass der Test überhaupt besteht. Sie haben bereits gesehen, wie Sie die übergebenen Argumente mit 'call_args_list' an' calculate_something' übergeben können. Danach ist es nur eine Frage des Vergleichs der strukturierten Daten mit dem, was Sie erwarten. –

Verwandte Themen