Библиотека макетов Python: есть ли способ получить соответствующие возвращаемые значения из магических вызовов?

При написании тестов Python с библиотекой mock я часто получаю «с какими аргументами вызывается метод», например:

from __future__ import print_function
import mock

m = mock.MagicMock(side_effect=lambda x: x * x)
m(4)
print("m called with: ", m.call_args_list)

(это напечатает m called with: [call(4)]). Вопрос. Есть ли способ получить возвращаемое значение (в данном случае 16)?

Подробности. В моем конкретном сценарии я хочу использовать side_effect для возврата подложного объекта: важно изучить этот объект, чтобы увидеть, что на нем вызывается. Например, «настоящий код» (нетестовый код) может написать:

myobj = m(4)
myobj.foo()

Использование side_effect кажется удобным способом вернуть новые подмоки объектов, но также сохранить call_args_list. Однако не похоже, что MagicMock хранит возвращаемые значения из функции side_effect... я ошибаюсь?


person gatoatigrado    schedule 02.08.2013    source источник


Ответы (5)


Это не то, для чего предназначены насмешки, если вы спросите меня. Если вы хотите подтвердить возвращаемое значение, вы должны вызвать функцию напрямую. Если вам нужно прыгать через обручи, чтобы зарегистрировать, что возвращает функция, ваш дизайн, скорее всего, ошибочен.

Если, например, у вас есть внутренняя функция, вы не должны тестировать внутреннюю функцию. Если вы хотите протестировать внутреннюю функцию, сделайте ее общедоступной.

Итак, вместо этого:

def spam(input):
    def eggs(nested):
        return nested*2
    input = eggs(nested)
    return eggs(input)

Сделай это:

def eggs(nested):
    return nested*2

def spam(input):
    input = eggs(nested)
    return eggs(input)
person siebz0r    schedule 11.02.2014

Вам не нужен side_effect, это то, что моки уже делают без него, они возвращают другие моки.

Вы делаете утверждения о них, делая mymock.return_value.assert_called_once_with(4)

Если бы вы это сделали, вы бы просто вернули макет, на который у вас была ссылка в другом месте из side_effect.

person Julian    schedule 02.08.2013
comment
Я хочу, чтобы другой макет вернулся, чтобы он был чем-то особенным. m(4) ​​— это не просто еще один MagicMock, а объект с некоторым состоянием (в моем случае — имитация сервера Redis). Спасибо хоть! - person gatoatigrado; 03.08.2013
comment
Затем настройте его с помощью m.return_value. - person Julian; 04.08.2013

Используйте 1_.

Например,

import unittest

import mock

def foo(m):
    myobj = m(4)
    myobj.foo()

import unittest

class TestFoo(unittest.TestCase):
    def test_foo(self):
        m2 = mock.MagicMock()
        m = mock.MagicMock(return_value=m2)

        foo(m)

        m.assert_called_once_with(4)
        m2.foo.assert_called_once_with()

if __name__ == '__main__':
    unittest.main()
person falsetru    schedule 03.08.2013
comment
Это требует модификации нетестирующего кода, который вызывает m(4). - person gatoatigrado; 04.08.2013

У вас есть объект call(). Вы хотите получить аргументы ВНУТРИ объекта вызова. То есть, если вы получаете

call_args_list[0] == call(3, 5)

вам нужны 3 и 5. объект call() может быть проиндексирован - в первую очередь обратитесь к элементу 0, который является списком фактических аргументов. Затем вам нужно 3, которое находится внутри него как 0-й элемент.

Получите это с помощью:

cargs = call_args_list[0]
assert cargs[0][0] == 3

>>> mockObjThing.someFunc.call_args_list[0]
call('argOne', 'argTwo') 
>>> # DAGNABBIT I want argOne!!!
>>> cargs = mockObjThing.someFunc.call_args_list[0]
>>> cargs
call('argOne', 'argTwo') 
>>> cargs[0]
('argOne', 'argTwo')
>>> cargs[0][0]
'argOne'
person Kevin J. Rice    schedule 17.09.2018

А как насчет параметра wraps?

class MyClass:
    def foo(self, x):
        return(x * x)

то вы можете утверждать вызов следующим образом:

my_obj = MyClass()
with patch.object(MyClass, 'foo', wraps=MyClass().foo) as mocked_foo:
    my_result = my_obj.foo(4)
    mocked_foo.assert_called_with(4)    

а my_result будет содержать фактический результат вызова foo()

person Federico Giraldi    schedule 10.09.2020