Издевательство над классом: Mock () или patch ()?


116

Я использую макет с Python, и мне было интересно, какой из этих двух подходов лучше (читайте: больше pythonic).

Метод первый : просто создайте фиктивный объект и используйте его. Код выглядит так:

def test_one (self):
    mock = Mock()
    mock.method.return_value = True 
    self.sut.something(mock) # This should called mock.method and checks the result. 
    self.assertTrue(mock.method.called)

Метод второй : используйте патч для создания имита. Код выглядит так:

@patch("MyClass")
def test_two (self, mock):
    instance = mock.return_value
    instance.method.return_value = True
    self.sut.something(instance) # This should called mock.method and checks the result. 
    self.assertTrue(instance.method.called)

Оба метода делают одно и то же. Я не уверен в различиях.

Может ли кто-нибудь просветить меня?


10
Как человек, который никогда не пробовал ни Mock (), ни патч, я чувствую, что первая версия более ясна и показывает, что вы хотите сделать, хотя я не понимаю реальной разницы. Я не знаю, поможет ли это или нет, но я подумал, что было бы полезно передать то, что может чувствовать непосвященный программист.
Майкл Бреннан

2
@MichaelBrennan: Спасибо за ваш комментарий. Это действительно полезно.
Sardathrion - против злоупотреблений SE

Ответы:


151

mock.patchсущество очень сильно отличается от mock.Mock. patch заменяет класс фиктивным объектом и позволяет работать с фиктивным экземпляром. Взгляните на этот фрагмент:

>>> class MyClass(object):
...   def __init__(self):
...     print 'Created MyClass@{0}'.format(id(self))
... 
>>> def create_instance():
...   return MyClass()
... 
>>> x = create_instance()
Created MyClass@4299548304
>>> 
>>> @mock.patch('__main__.MyClass')
... def create_instance2(MyClass):
...   MyClass.return_value = 'foo'
...   return create_instance()
... 
>>> i = create_instance2()
>>> i
'foo'
>>> def create_instance():
...   print MyClass
...   return MyClass()
...
>>> create_instance2()
<mock.Mock object at 0x100505d90>
'foo'
>>> create_instance()
<class '__main__.MyClass'>
Created MyClass@4300234128
<__main__.MyClass object at 0x100505d90>

patchзаменяет MyClassтаким образом, чтобы вы могли контролировать использование класса в вызываемых вами функциях. После исправления класса ссылки на класс полностью заменяются имитирующим экземпляром.

mock.patchобычно используется, когда вы тестируете что-то, что создает новый экземпляр класса внутри теста. mock.Mockпримеры более ясны и предпочтительнее. Если ваш self.sut.somethingметод создал экземпляр MyClassвместо того, чтобы получать экземпляр в качестве параметра, то mock.patchздесь будет уместно.


2
@ D.Shawley, как исправить класс, созданный внутри другого класса, который нужно тестировать.
ravi404

4
@ravz - прочтите "Куда патчить " . Это одна из самых сложных вещей для правильной работы.
Д.Шоули

Мой тестовый тест похож на второй метод . Я хочу, чтобы экземпляр MyClass вызвал исключение. Я пробовал как mock.side_effect, так и mock.return_value.side_effect, и они не работали. Что мне делать?
Хуссейн

6
@ D.Shawley Ссылка не работает, теперь ее можно найти здесь: "Где установить исправление"
RazerM

2
Чтобы залатать объект класса см stackoverflow.com/questions/8469680/...
storm_m2138

27

У меня есть видео на YouTube по этому поводу.

Краткий ответ: используйте, mockкогда вы проходите мимо того, что хотите высмеять, а patchесли нет. Из этих двух настоятельно рекомендуется использовать mock, потому что это означает, что вы пишете код с правильной инъекцией зависимостей.

Глупый пример:

# Use a mock to test this.
my_custom_tweeter(twitter_api, sentence):
    sentence.replace('cks','x')   # We're cool and hip.
    twitter_api.send(sentence)

# Use a patch to mock out twitter_api. You have to patch the Twitter() module/class 
# and have it return a mock. Much uglier, but sometimes necessary.
my_badly_written_tweeter(sentence):
    twitter_api = Twitter(user="XXX", password="YYY")
    sentence.replace('cks','x') 
    twitter_api.send(sentence)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.