2010-09-30 10 views
46

Załóżmy, że mam następujący kod w badanej jednostki Python:twierdzą, że metoda została wywołana w badanej jednostki Python

aw = aps.Request("nv1") 
aw2 = aps.Request("nv2", aw) 

Czy istnieje prosty sposób, aby twierdzić, że w szczególny sposób (w moim przypadku aw.Clear()) został wywołany podczas drugiej linii testu? na przykład jest tam coś takiego:

#pseudocode: 
assertMethodIsCalled(aw.Clear, lambda: aps.Request("nv2", aw)) 

Odpowiedz

80

używam Mock na to:

from mock import patch 
from PyQt4 import Qt 

@patch.object(Qt.QMessageBox, 'aboutQt') 
def testShowAboutQt(self, mock): 
    self.win.actionAboutQt.trigger() 
    self.assertTrue(mock.called) 

Twoim przypadku, może to wyglądać tak:

import mock 

def testClearWasCalled(self): 
    aw = aps.Request("nv1") 
    with patch.object(aw, 'Clear') as mock: 
     aw2 = aps.Request("nv2", aw) 

    mock.assert_called_with(42) # or mock.assert_called_once_with(42) 

Mock obsługuje sporo przydatnych funkcji, w tym sposobów łata przedmiot lub moduł , a także sprawdzanie, czy została wywołana właściwa funkcja itp.

Caveat emptor! (Kupujący strzeżcie się!)

Jeśli błędnie assert_called_with (do assert_called_once lub assert_called_wiht) swój test może nadal działać, jak Mock będzie, że jest to funkcja wyśmiewany i szczęśliwie iść, chyba że używasz autospec=true. Aby uzyskać więcej informacji, przeczytaj assert_called_once: Threat or Menace.

+3

+1 za dyskretne oświecenie mojego świata wspaniałym modułem Mock. –

+0

@RonCohen: Tak, to jest niesamowite, i cały czas się poprawia. :) – Macke

+1

Podczas korzystania z próbnego jest zdecydowanie do zrobienia, odradzam używanie assert_called_once, po prostu nie istnieje :) – FelixCQ

4

Można kpić z aw.Clear, ręcznie lub za pomocą ramy testowania jak pymox. Ręcznie, można to zrobić za pomocą mniej więcej tak:

class MyTest(TestCase): 
    def testClear(): 
    old_clear = aw.Clear 
    clear_calls = 0 
    aw.Clear = lambda: clear_calls += 1 
    aps.Request('nv2', aw) 
    assert clear_calls == 1 
    aw.Clear = old_clear 

Korzystanie pymox, że zrobisz to tak:

class MyTest(mox.MoxTestBase): 
    def testClear(): 
    aw = self.m.CreateMock(aps.Request) 
    aw.Clear() 
    self.mox.ReplayAll() 
    aps.Request('nv2', aw) 
+0

Lubię to podejście zbyt, chociaż nadal chcę old_clear aby sprawdził. To wyjaśnia, co się dzieje. –

7

Tak, mogę ci dać zarys ale mój Python jest trochę zardzewiały i jestem zbyt zajęty, aby szczegółowo wyjaśnić.

Zasadniczo trzeba umieścić pełnomocnictwa w sposób, który nazywamy oryginalne, np:

class fred(object): 
    def blog(self): 
    print "We Blog" 


class methCallLogger(object): 
    def __init__(self, meth): 
    self.meth = meth 

    def __call__(self, code=None): 
    self.meth() 
    # would also log the fact that it invoked the method 

#example 
f = fred() 
f.blog = methCallLogger(f.blog) 

Ten StackOverflow answer o wpłacone mogą pomóc w zrozumieniu powyższych.

Bardziej szczegółowo:

Chociaż odpowiedź została przyjęta ze względu na ciekawą dyskusję z Glenn i posiadające kilka minut za darmo, chciałem, aby powiększyć na moją odpowiedź:

# helper class defined elsewhere 
class methCallLogger(object): 
    def __init__(self, meth): 
    self.meth = meth 
    self.was_called = False 

    def __call__(self, code=None): 
    self.meth() 
    self.was_called = True 

#example 
class fred(object): 
    def blog(self): 
    print "We Blog" 

f = fred() 
g = fred() 
f.blog = methCallLogger(f.blog) 
g.blog = methCallLogger(g.blog) 
f.blog() 
assert(f.blog.was_called) 
assert(not g.blog.was_called) 
+0

ładny. Dodałem liczbę połączeń do metCallLogger, dzięki czemu mogę ją potwierdzić. –

+0

To na podstawie dokładnego, samodzielnego rozwiązania, które dostarczyłem? Poważnie? –

+0

@Glenn Jestem bardzo nowy w Pythonie - może twój jest lepszy - po prostu jeszcze tego nie rozumiem. Spędzę trochę czasu, próbując go wypróbować. –

13

I nie jestem świadomy niczego wbudowanego. Jest to dość proste do wykonania:

class assertMethodIsCalled(object): 
    def __init__(self, obj, method): 
     self.obj = obj 
     self.method = method 

    def called(self, *args, **kwargs): 
     self.method_called = True 
     self.orig_method(*args, **kwargs) 

    def __enter__(self): 
     self.orig_method = getattr(self.obj, self.method) 
     setattr(self.obj, self.method, self.called) 
     self.method_called = False 

    def __exit__(self, exc_type, exc_value, traceback): 
     assert getattr(self.obj, self.method) == self.called, 
      "method %s was modified during assertMethodIsCalled" % self.method 

     setattr(self.obj, self.method, self.orig_method) 

     # If an exception was thrown within the block, we've already failed. 
     if traceback is None: 
      assert self.method_called, 
       "method %s of %s was not called" % (self.method, self.obj) 

class test(object): 
    def a(self): 
     print "test" 
    def b(self): 
     self.a() 

obj = test() 
with assertMethodIsCalled(obj, "a"): 
    obj.b() 

Wymaga to, że sam obiekt nie będzie modyfikować self.b, który jest prawie zawsze prawdziwe.

+0

Powiedziałem, że mój Python był zardzewiały, chociaż przetestowałem swoje rozwiązanie, aby się upewnić, że działa :-) Zinternalizowałem Python przed wersją 2.5, w rzeczywistości nigdy nie używałem 2.5 dla żadnego znaczącego Pythona, ponieważ musieliśmy zamrozić w wersji 2.3 dla zgodności z lib. Przeglądając twoje rozwiązanie znalazłem http://effbot.org/zone/python-with-statement.htm jako ładny, jasny opis. Chciałbym pokornie zasugerować, że moje podejście wygląda na mniejsze i może być łatwiejsze do zastosowania, jeśli chciałbyś więcej niż jeden punkt logowania, zamiast zagnieżdżać "z". Naprawdę chciałbym, żebyś wyjaśnił, czy są jakieś szczególne korzyści z twojego. –

+0

@Andy: Twoja odpowiedź jest mniejsza, ponieważ jest częściowa: nie testuje wyników, nie przywraca oryginalnej funkcji po teście, dzięki czemu możesz dalej używać obiektu i musisz wielokrotnie pisać kod, aby wykonać wszystko to za każdym razem, gdy piszesz test. Liczba linii kodu pomocniczego nie jest ważna; ta klasa jest umieszczona we własnym module testowym, a nie w linii w docstringu - w rzeczywistym teście zajmuje jedną lub dwie linie kodu. –

+0

+1 za korzystanie z menedżera kontekstu, czysty pomysł! –

10

Tak, jeśli używasz Pythona 3.3+. Możesz użyć wbudowanego unittest.mock do potwierdzenia metody o nazwie. W przypadku Pythona 2.6 lub nowszego użyj ruchomego backportu Mock, czyli tego samego.

Oto krótki przykład w przypadku:

from unittest.mock import MagicMock 
aw = aps.Request("nv1") 
aw.Clear = MagicMock() 
aw2 = aps.Request("nv2", aw) 
assert aw.Clear.called 
Powiązane problemy