Инверсия зависимостей крайне мощная техника, которая работает не только с функциями, но и с объектами. Рассмотрим её глубже на примере HTTP-запросов и познакомимся с таким понятием как "заглушка" (stub).
Предположим, что у нас есть функция, которая анализирует приватные репозитории организации на GitHub и возвращает те, что являются приватными форками (репозитории "отпочкованные" от основного репозитория):
# Библиотека для работы с GitHub API
from github import Github
def get_private_fork_names(username):
client = Github('access_token')
# Клиент выполняет запрос на гитхаб и возвращает
# список приватных репозиториев указанной организации
repos = client.get_user(username).get_repos(type='private')
# Оставляем только имена приватных форков
return [repo.name for repo in repos if repo.fork == True]
Давайте её протестируем. Что мы хотим от этой функции? В первую очередь убедиться, что она работает правильно – возвращает массив имен приватных форков. Идеальный тест выглядел бы так:
def test_get_private_fork_names():
names = get_private_fork_names('hexlet')
assert names == # ожидаемый список имен
К сожалению, не всё так просто. Внутри функции выполняется HTTP-запрос. Прикинем, какие проблемы из-за этого могут возникнуть:
В данном примере HTTP-запрос воспринимается как помеха к тому, чтобы протестировать нашу основную логику. Мы доверяем PyGithub и его библиотеке github, то есть нам не нужно проверять, что она работает правильно (иначе можно свихнуться, если не доверять никому).
Из предыдущего урока мы узнали о нескольких способах выхода из этой ситуации и теперь можем применить один из них.
Для использования инверсии зависимости, добавим вторым аргументом функции сам клиент github. Это позволит подменить его в тестах:
from github import Github
# Клиент для работы с Github передаётся снаружи и его можно подменить
def get_private_fork_names(username, client=Github()):
# Логика повторяется, за исключением работы с клиентом
repos = client.get_user(username).get_repos(type='private')
return [repo.name for repo in repos if repo.fork == True]
Нам придётся реализовать фейковый (ненастоящий) клиент, который ведёт себя примерно так же, как и реальный Github, за исключением того, что он не выполняет сетевых запросов. Также нам нужно описать конкретные данные, которые вернут вызовы get_user()
и get_repos()
. Только в таком случае мы сможем протестировать, что функция get_private_fork_names()
работает корректно.
# Структура этого класса описывает только ту часть,
# которая необходима для вызова client.get_user.get_repos
class GithubFake:
# Здесь мы описываем желаемые данные, которые вернутся в тесте
def __init__(self, data):
self.data = data
# Возвращаем сами себя, чтобы иметь возможность вызвать get_repos() по цепочке
def get_user(self, name):
return self
# Возвращаем переданные в начале данные
def get_repos(self):
return self.data
И сам тест с использованием этого клиента:
from github_fake import GithubFake
def test_get_private_fork_names():
data = # Данные, которые должен вернуть get_repos
client = GithubFake(data)
names = get_private_fork_names('hexlet', client=client)
assert names == # ожидаемый список имен
В тестировании для подобных фейковых объектов (или функций) есть специальное название – стаб (stub). Стаб заменяет реальный объект или функцию, позволяя избежать выполнения побочных эффектов или сделать код детерминированным. Стаб не используется для проверки чего-либо, он лишь позволяет изолировать ту часть, которая "мешает" тестированию основной логики.
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт