#python #unit-testing #mocking
#python #модульное тестирование #насмешливый
Вопрос:
Вот функция, которую я хочу протестировать:
def send_something():
conn = lib.conn.SSH1
conn.open()
conn.send('lsn')
if 'home' not in conn.recbuf:
return lib.FAIL
conn.send('whoamin')
if USER not in conn.recbuf:
return lib.FAIL
return lib.PASS
Каждый раз, когда я вызываю conn.send(), результат вызова сохраняется в conn.recbuf. Чтобы проверить это, мне нужно, чтобы conn.recbuf отличался при каждом его вызове. (Я знаю, что мог бы просто изменить conn.recbuf на строку, содержащую как home, так и USER, но это не будет работать для более сложных функций)
Вот что я пока придумал в своем тесте:
@mock.patch.object(demo_sequence.lib, 'conn')
def test_send_something(mock_conn):
mock_conn.SSH1.recbuf = 'home'
assert demo_sequence.lib.PASS == demo_sequence.send_something()
mock_conn.SSH1.send.assert_any_call('lsn')
mock_conn.SSH1.send.assert_any_call('whoamin')
Очевидно, что это не удается, потому что conn.recbuf является только ‘home’ и не содержит USER.
Мне нужно что-то вроде conn.recbuf.side_effect = [‘home’, USER], но он будет работать, если просто ссылаться на recbuf, а не вызывать его.
Комментарии:
1. Разве вы не хотели бы просто издеваться
send
, чтобы он не делал ничего, кроме обновленияrecbuff
? Трудно предоставить хорошее решение, не зная, какой тип ` conn` на самом деле.2. Библиотека conn странная (плохая). SSH1 также должен быть изменен, потому что он не всегда существует. Вот почему я выбрал mock в качестве уровня lib.conn.
3. Я мог бы также порекомендовать вместо жесткого кодирования значения
conn
insend_something
сделать его аргументом функции. Тогда вы можете передать любой объект, который вы хотите, в качестве аргумента вместо того, чтобы использоватьmock.patch
вообще.4. Если вы проводите модульное тестирование send_something, то, вероятно, лучше полностью имитировать conn. Также возможно выполнить другой, особый вид теста для библиотеки lib.conn, если вы хотите убедиться, что он соответствует контракту. Кроме того, можно создать оболочку / адаптер вокруг «плохой» библиотеки — если ваш код использует библиотеку во многих местах.
Ответ №1:
Если вы можете изменить conn.recbuf на свойство, вы могли бы использовать PropertyMock для достижения желаемого эффекта.
from unittest import mock
class Dummy:
def __init__(self, myattribute):
self._myattribute = myattribute
@property
def myattribute(self):
return self._myattribute
def test():
with mock.patch('__main__.Dummy.myattribute', new_callable=mock.PropertyMock) as mocked_attribute:
mocked_attribute.side_effect = [4,5,6]
d = Dummy("foo")
print(d.myattribute)
print(d.myattribute)
print(d.myattribute)
Однако для вашей реальной проблемы, я думаю, комментарии к вашему вопросу, похоже, включают разумные подходы.
Комментарии:
1. Этот подход действительно удовлетворяет мои потребности. Я видел несколько комментариев и предыдущих ответов, в которых говорилось об использовании PropertyMock, но я никогда не проверял, является ли recbuf реальным свойством. (имеет свойство decorator). Спасибо за дальнейшее объяснение.
Ответ №2:
То, что вы ищете, это side_effect
:https://docs.python.org/3/library/unittest.mock.html
Допустим, mock
это ваш макет объекта. Затем вы можете сделать это:
mock.side_effect = [a,b,c]
затем при каждом вызове mock
возвращается следующее значение из списка.
mock() # -> a
mock() # -> b
mock() # -> c
Комментарии:
1. Отклонено, потому что это именно то, что still learning упомянул в своем последнем предложении, а также объяснил, почему это не работает: ему нужно именно такое поведение для атрибутов, а не для вызываемых объектов.
2. Понял. Вы можете добиться того же для атрибутов, издеваясь над
__getattr__
методом, но это может быть немного запутанным.3. Можете ли вы подробнее рассказать о mocking getattr для достижения решения моей проблемы?
4. Когда вы выполняете a.x, он преобразуется в a.__getattr__(‘x’). Итак, вы можете попробовать издеваться над этим.
Ответ №3:
Ранее я сталкивался с аналогичной проблемой и нашел решение с помощью PropertyMock
. В моем случае я хотел сохранить значение в другом месте тестового примера для утверждения, вызывая метод через side_effect
, когда свойство было установлено для класса, над которым я издевался.
Я думаю, что это должно сработать и в вашем случае (если я понял, что вы пытаетесь сделать).
Решение состоит в том, чтобы использовать PropertyMock
для вашего свойства recbuf
и поместить side_effect
туда:
from unittest.mock import MagicMock, PropertyMock
conn = MagicMock()
type(conn).recbuf = PropertyMock(side_effect=['home', USER])
Побочный эффект будет вызываться при каждом обращении к свойству recbuf
, в данном случае повторяясь ['home', USER]
каждый раз. Я думаю, вам также нужно будет исправить это USER
значение, чтобы вы не получали ошибку имени.
Надеюсь, это сработает / поможет!