В предыдущем уроке мы тестировали гипотетическую функцию get_private_fork_names(org)
, применяя инверсию зависимостей.
Вспомним содержимое этой функции в ее исходном виде:
from github import Github
def get_private_fork_names(username):
client = Github('access_token')
# Клиент выполняет запрос на GitHub и возвращает
# список приватных репозиториев указанной организации
repos = client.get_user(username).get_repos(type='private')
# Оставляем только имена приватных форков
return [repo.name for repo in repos if repo.fork == True]
В некоторых ситуациях инверсия зависимостей подходит идеально. В других случаях из-за нее код становится сложнее и запутаннее, особенно если зависимости требуются глубоко в стеке вызовов — придется пробрасывать зависимость через все промежуточные функции.
Но есть способ, который позволяет добраться до нужных вызовов и изменить их даже без инверсии зависимостей. Именно его мы изучим в этом уроке.
Что такое манкипатчинг
Python позволяет подменять модули, классы и методы прямо во время работы программы из любого ее места:
# Подменяем get_repos так, чтобы не выполнялся сетевой запрос
# Где-то в другом файле
from github import Github, NamedUser
# NamedUser возвращается после вызова get_user()
# https://github.com/PyGithub/PyGithub/blob/master/github/NamedUser.py
def fake_get_repos(self):
print('Nothing happened!')
# После выполнения этого кода библиотека Github меняет свое поведение не только
# в этом модуле, но и вообще по всей программе
NamedUser.get_repos = fake_get_repos
client = Github()
# Вызывается подмененный get_repos
client.get_user().get_repos()
# => 'Nothing happened!'
Так происходит изменение модулей и классов прямо во время работы программы — в рантайме. Этот процесс называется манкипатчингом (monkey patching). Он считается опасной практикой при написании обычного кода в Python, но он очень популярен и удобен в библиотеках или во время тестирования.
Библиотека pook — типичный пример хорошего использования манкипатчинга в реальном программировании.
С ее помощью подменяются запросы без прямого взаимодействия с HTTP-клиентом, как в случае инверсии зависимостей:
import pook
import requests # Популярный HTTP-клиент
@pook.on
def test_my_api():
# Патчим запрос на конкретную страницу и указываем, что вернуть
pook.get(
'http://twitter.com/api/1/foobar',
reply=404,
response_json={'error': 'not found'}
)
# Сам запрос через библиотеку requests
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 404
assert resp.json() == {"error": "not found"}
Pook заменяет внутри библиотеки urllib3 метод urlopen()
, который используется библиотеками поверх urllib3 для выполнения HTTP-запросов.
Рассмотрим пример использования:
@pook.on
def test_get_private_fork_names():
pook.get(
'https://api.github.com/orgs/hexlet/repos',
reply=200,
response_json=[{ 'fork': True, 'name': 'one' }, { 'fork': False, 'name': 'two' }]
)
names = get_private_fork_names('hexlet')
assert names == # ожидаемый список имен
Вызов pook.get(url)
задает полный адрес страницы, запрос к которой надо перехватить. Pook анализирует все выполняемые запросы и подменяет только тот, который соответствует этим параметрам.
Параметры reply
и response_json
описывают ответ, который нужно вернуть по данному запросу. В самом простом случае достаточно указать код возврата. В нашей ситуации нужен не только код, но и данные. Именно на этих данных мы и проверяем работу функции get_private_fork_names()
.
В чем плюсы и минусы такого способа работы?
Главный плюс в том, что такой способ тестирования практически универсальный. Его можно использовать с любым кодом, причем без необходимости править сам код. Программа даже не будет догадываться, что ее тестируют.
Минус же заключается в том, что тестирование «черным ящиком» превращается в тестирование «прозрачным ящиком». Это значит, что тест знает про устройство тестируемого кода и зависит от внутренностей. Это делает тесты хрупкими. Функция может измениться без потери работоспособности, но тесты придется переписывать, потому что они завязаны на конкретные значения домена, страниц и формата возвращаемых данных.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.