2010-09-30 2 views
46

Предположим, что у меня есть следующий код в модульном тесте Python:утверждают, что метод был вызван в модульном тесте Python

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

Есть простой способ утверждать, что конкретный метод (в моем случае aw.Clear()) был вызван во второй строке теста? например есть что-то вроде этого:

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

ответ

80

Я использую Mock для этого:

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) 

В вашем случае, это может выглядеть следующим образом:

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 поддерживает довольно много полезных функций, включая способы залатать объект или модуль , а также проверку того, что правильная вещь была вызвана и т. д.

Caveat emptor! (Покупатель берегись!)

Если вы ошиблись при вводе assert_called_withassert_called_once или assert_called_wiht) ваш тест еще может работать, так как Мок будет думать, что это издевались функция и счастливо идти вместе, если вы не используете autospec=true. Для получения дополнительной информации читайте assert_called_once: Threat or Menace.

+3

+1 за дискретно просвещающий мой мир с замечательным модулем Mock. –

+0

@RonCohen: Да, это довольно удивительно, и все время улучшаться. :) – Macke

+1

При использовании mock, безусловно, путь, я бы посоветовал использовать assert_called_once, просто не существует :) – FelixCQ

4

Вы можете издеваться из aw.Clear, либо вручную, либо с помощью рамки тестирования как pymox. Вручную, вы бы сделать это с помощью что-то вроде этого:

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 

Используя pymox, вы бы сделать это следующим образом:

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

Мне нравится этот подход тоже, хотя я все еще хочу, чтобы old_clear дозвонились. Это делает очевидным, что происходит. –

7

Да, я могу дать вам план, но мой Python является бит ржавый, и я слишком занят, чтобы объяснить подробно.

В принципе, вам нужно поставить прокси в метод, который будет вызывать оригинал, например:

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) 

Это StackOverflow answer о вызываемая может помочь вам понять выше.

Более подробно:

Хотя ответ был принят из-за интересную дискуссию с Гленн и имея несколько минут бесплатно, я хотел бы увеличить на мой ответ:

# 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

приятно. Я добавил счетчик вызовов methCallLogger, чтобы я мог утверждать это. –

+0

Это полное, автономное решение, которое я предоставил? Шутки в сторону? –

+0

@Glenn Я очень новичок в Python - может быть, ваш лучше, я все еще не понимаю. Я потрачу немного времени на то, чтобы попробовать. –

13

I «Не знаю ничего встроенного. Это довольно просто реализовать:

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() 

Это требует, чтобы сам объект не будет изменять self.b, что почти всегда верно.

+0

Я сказал, что мой Python был ржавым, хотя я проверял свое решение, чтобы убедиться, что он работает :-) Я усвоил Python до версии 2.5, на самом деле я никогда не использовал 2.5 для любого значительного Python, поскольку нам приходилось замораживать в версии 2.3 для совместимости с lib. При рассмотрении вашего решения я нашел http://effbot.org/zone/python-with-statement.htm как хорошее четкое описание. Я смиренно предлагаю, чтобы мой подход выглядел меньше, и его было бы легче применить, если вы захотите более одного пункта ведения журнала, а не вложенных «с». Мне бы очень хотелось, чтобы вы объяснили, есть ли какие-то особые преимущества. –

+0

@ Andy: ваш ответ меньше, потому что он частично: он фактически не проверяет результаты, он не восстанавливает исходную функцию после теста, поэтому вы можете продолжать использовать этот объект, и вам приходится многократно писать код для выполнения все это снова каждый раз, когда вы пишете тест. Количество строк кода поддержки не важно; этот класс входит в свой собственный модуль тестирования, а не встроен в docstring - это занимает одну или две строки кода в реальном тесте. –

+0

+1 для использования контекстного менеджера, аккуратная идея! –

10

Да, если вы используете Python 3.3+. Вы можете использовать встроенный метод unittest.mock для вызова метода assert. Для Python 2.6+ используйте скользящий backport Mock, что то же самое.

Вот краткий пример в вашем случае:

from unittest.mock import MagicMock 
aw = aps.Request("nv1") 
aw.Clear = MagicMock() 
aw2 = aps.Request("nv2", aw) 
assert aw.Clear.called 
Смежные вопросы