Утверждать, что метод вызывался в модульном тесте Python

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

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

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

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

Ответы на вопрос(5)

Решение Вопроса

я используюиздеваться (который сейчасunittest.mock на py3.3 +) для этого:

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

Пусть покупатель будет бдителен! (Предостережение для покупателя!)

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

 Ron Cohen26 апр. 2012 г., 17:09
+1 за дискретное освещение моего мира с помощью замечательного модуля Mock.
 Brett Jackson26 дек. 2018 г., 22:40
Если вы обнаружите, что это неправильно исправляет, убедитесь, что вы исправляете в нужном месте. Это может быть полезно:docs.python.org/3/library/unittest.mock.html#where-to-patch
 Macke20 июн. 2013 г., 07:52
это было удалено в более поздних версиях. Мои тесты все еще используют это. :)
 Macke09 окт. 2015 г., 17:16
Wrt последнее обновление и блог, который я связал. Теперь мне интересно, работали ли мои тесты правильно или нет. Ну что ж. Это было 4 года назад в другой компании. : - |
 rgilligan13 июл. 2017 г., 18:18
Стоит повторить, насколько полезно использовать autospec = True для любого смоделированного объекта, потому что он действительно может укусить вас, если вы неправильно напишите метод assert.
 Macke26 апр. 2012 г., 20:52
@RonCohen: Да, это довольно удивительно, и все лучше и лучше. :)
 FelixCQ19 июн. 2013 г., 20:09
Хотя использование mock - определенно лучший способ, я бы посоветовал не использовать assert_called_once, поскольку просто не существует :)

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

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

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

# 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)
 Mark Heath30 сент. 2010 г., 13:35
@ Гленн Я очень новичок в Python - может быть, ваш лучше - я просто еще не все понимаю. Я потрачу немного времени позже, чтобы попробовать это.
 Mark Heath30 сент. 2010 г., 13:00
отлично. Я добавил счетчик вызовов в methCallLogger, чтобы я мог утверждать это.
 Glenn Maynard30 сент. 2010 г., 13:19
Это из-за тщательного, автономного решения, которое я предоставил? Шутки в сторону?
 Matt Messersmith30 нояб. 2016 г., 14:52
Это, безусловно, самый простой и понятный ответ. Действительно хорошая работа!

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, что почти всегда верно.

 Florian Brucker20 июн. 2013 г., 08:08
+1 за использование менеджера контекста, отличная идея!
 Glenn Maynard01 окт. 2010 г., 22:26
@ Энди: Ваш ответ меньше, потому что он неполный: он на самом деле не проверяет результаты, он не восстанавливает исходную функцию после теста, поэтому вы можете продолжать использовать объект, и вам приходится многократно писать код, чтобы сделать все это снова каждый раз, когда вы пишете тест. Количество строк кода поддержки не имеет значения; этот класс идет в своем собственном модуле тестирования, а не в строке документа - это занимает одну или две строки кода в реальном тесте.
 Andy Dent01 окт. 2010 г., 13:08
Я сказал, что мой Python был ржавым, хотя я тестировал свое решение, чтобы убедиться, что оно работает :-) Я усвоил Python до версии 2.5, фактически я никогда не использовал 2.5 для какого-либо значимого Python, поскольку нам пришлось заморозить на 2.3 для совместимости с lib. При рассмотрении вашего решения я нашелeffbot.org/zone/python-with-statement.htm как хорошее четкое описание. Я бы смиренно предложил, чтобы мой подход выглядел меньше, и его было бы проще применить, если бы вы хотели более одной точки ведения журнала, а не вложенных «с». Я бы очень хотел, чтобы вы объяснили, есть ли у вас какие-то особые преимущества.

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

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

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

Вы можете издеваться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)
 Mark Heath30 сент. 2010 г., 13:04
Мне также нравится этот подход, хотя я все еще хочу, чтобы old_clear был вызван. Это делает очевидным, что происходит.

Ваш ответ на вопрос