Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Манкипатчинг Python: Продвинутое тестирование

В предыдущем уроке мы тестировали гипотетическую функцию 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().

В чем плюсы и минусы такого способа работы?

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

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


Дополнительные материалы

  1. Кассеты (VCR)
  2. Responses (Для Requests)

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 28 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»