2

Например, у меня есть модуль (foo.py) со следующим кодом:метод Patch только в одном модуле

import requests 

def get_ip(): 
    return requests.get('http://jsonip.com/').content 

И модуль bar.py с кодом: похож

import requests 

def get_fb(): 
    return requests.get('https://fb.com/').content 

Я просто не могу понять, почему следующий случается:

from mock import patch 

from foo import get_ip 
from bar import get_fb 

with patch('foo.requests.get'): 
    print(get_ip()) 
    print(get_fb()) 

Они два издевались: <MagicMock name='get().content' id='4352254472'> <MagicMock name='get().content' id='4352254472'> Похоже, что исправляется только foo.get_ip метод из-за with patch('foo.requests.get'), но это не так. Я знаю, что могу просто получить bar.get_fb, вызывая из области with, но бывают случаи, когда я просто запускаю в контекстном менеджере один метод, который вызывает много других, и я хочу исправить requests только в одном модуле. Есть ли способ решить это? Без изменения импорта в модуле

+0

Я думаю, с помощью декоратора пропатчить функции следует сделать трюк. – Dan

ответ

2

Два местоположения foo.requests.get и bar.requests.get относятся к одному и тому же объекту, поэтому издевайтесь над ним в одном месте, и вы издеваетесь над ним в другом месте.

Представьте, как вы можете использовать патч. Вы должны найти, где находится символ, и заменить символ макетным объектом. При выходе из контекста вам нужно будет восстановить исходное значение символа. Что-то вроде (непроверенные):

class patch(object): 
    def __init__(self, symbol): 
     # separate path to container from name being mocked 
     parts = symbol.split('.') 
     self.path = '.'.join(parts[:-1] 
     self.name = parts[-1] 
    def __enter__(self): 
     self.container = ... lookup object referred to by self.path ... 
     self.save = getattr(self.container, name) 
     setattr(self.container, name, MagicMock()) 
    def __exit__(self): 
     setattr(self.container, name, self.save) 

Так что ваша проблема заключается в том, что вы насмешливый объект в модуле запроса, который затем имеют в виду как из Foo и бар.


По предложению @ elethan, вы могли издеваться модуль запросов в обув, и даже обеспечить побочные эффекты на методе ПОЛУЧАЕТЕ:

from unittest import mock 
import requests 

from foo import get_ip 
from bar import get_fb 

def fake_get(*args, **kw): 
    print("calling get with", args, kw) 
    return mock.DEFAULT 

replacement = mock.MagicMock(requests) 
replacement.get = mock.Mock(requests.get, side_effect=fake_get, wraps=requests.get) 
with mock.patch('foo.requests', new=replacement): 
    print(get_ip()) 
    print(get_fb()) 

Более прямое решение заключается в изменении кода так что foo и bar вытащите ссылку на get прямо на свое пространство имен.

foo.py:

from requests import get 

def get_ip(): 
    return get('http://jsonip.com/').content 

bar.py:

from requests import get 

def get_ip(): 
    return get('https://fb.com/').content 

main.py:

from mock import patch 

from foo import get_ip 
from bar import get_fb 

with patch('foo.get'): 
    print(get_ip()) 
    print(get_fb()) 

производство:

<MagicMock name='get().content' id='4350500992'> 
b'<!DOCTYPE html>\n<html lang="en" id="facebook" ... 

обновление с более полным объяснением, и с лучшим решением (2016-10-15)

Примечания: добавлен wraps=requests.get для вызова основной функции после побочного эффекта.

+0

Насколько я могу судить, вы можете получить тот же эффект, сохранив foo.py и bar.py то же самое, и насмехаясь над 'foo.requests' вместо' foo.requests.get' – elethan

+0

Спасибо, но я также знаю это решение и упомянуто в сообщении, что «Без изменения импорта в модуле». Я надеюсь, что есть решение, которое не требует изменения импорта скриптов. – hasam

+0

@elethan this works, thnx;) Но почему, когда насмехается 'foo.requests', он издевается только на' запросы' в 'foo.py', но когда насмехается' foo. request.get', он mocks 'foo.py' и' bar.py'. Можете ли вы написать полный ответ, почему это происходит? – hasam

1

Не украсть гром @Neapolitan «s, но другой вариант был бы просто издеваются foo.requests вместо foo.requests.get:

with patch('foo.requests'): 
    print(get_ip()) 
    print(get_fb()) 

Я думаю, что причина, почему оба метода получают издевались в вашем случае является то, что, поскольку requests.get не импортируются явно в foo.py, mock придется искать метод в requests модуля и издеваются его там, а не издевается его в requests объект уже импортирован в foo, так что, когда bar позже импортирует requests и получает доступ к requests.get, он получает издевательскую версию. Однако, если вы используете patchfoo.requests, вы просто исправляете объект модуля, уже импортированный в foo, а исходный модуль requests не будет затронут.

Хотя не особенно полезны для этой конкретной проблемы, this article очень полезно для понимания тонкостей patch

+0

Есть ли способ поставить 'side_effect' на метод' request.get' с помощью этого подхода? – hasam

+0

@hasam Да, до тех пор, пока вы «импортируете foo» в свой тестовый модуль, вы должны иметь возможность делать 'foo.requests.get.side_effect = whatever_side_effect', поскольку' foo.requests.get' будет 'Mock' объект. – elethan

 Смежные вопросы

  • Нет связанных вопросов^_^