0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Инверсия зависимостей крайне мощная техника, которая работает не только с функциями, но и с объектами. Рассмотрим её глубже на примере HTTP-запросов и познакомимся с таким понятием как "заглушка" (stub).</p>
1
<p>Инверсия зависимостей крайне мощная техника, которая работает не только с функциями, но и с объектами. Рассмотрим её глубже на примере HTTP-запросов и познакомимся с таким понятием как "заглушка" (stub).</p>
2
<p>Предположим, что у нас есть функция, которая анализирует приватные репозитории организации на GitHub и возвращает те, что являются форками (репозитории "отпочкованные" от основного репозитория):</p>
2
<p>Предположим, что у нас есть функция, которая анализирует приватные репозитории организации на GitHub и возвращает те, что являются форками (репозитории "отпочкованные" от основного репозитория):</p>
3
<p>Давайте её протестируем. Что мы хотим от этой функции? В первую очередь убедиться, что она работает правильно - возвращает массив форков у конкретного пользователя. Идеальный тест выглядел бы так:</p>
3
<p>Давайте её протестируем. Что мы хотим от этой функции? В первую очередь убедиться, что она работает правильно - возвращает массив форков у конкретного пользователя. Идеальный тест выглядел бы так:</p>
4
<p>К сожалению, не всё так просто. Внутри функции выполняется HTTP-запрос. Прикинем, какие проблемы из-за этого могут возникнуть:</p>
4
<p>К сожалению, не всё так просто. Внутри функции выполняется HTTP-запрос. Прикинем, какие проблемы из-за этого могут возникнуть:</p>
5
<ol><li>Нестабильная сеть может тормозить выполнение тестов и приводить к "фантомным" ошибкам. Тесты будут иногда проходить, иногда нет.</li>
5
<ol><li>Нестабильная сеть может тормозить выполнение тестов и приводить к "фантомным" ошибкам. Тесты будут иногда проходить, иногда нет.</li>
6
<li>У сервисов подобных<em>github.com</em>установлены ограничения на запросы в секунду, в час, день и так далее. Со 100% вероятностью тесты начнут упираться в эти лимиты. Более того, есть шанс что машина с которой идут запросы, будет заблокирована.</li>
6
<li>У сервисов подобных<em>github.com</em>установлены ограничения на запросы в секунду, в час, день и так далее. Со 100% вероятностью тесты начнут упираться в эти лимиты. Более того, есть шанс что машина с которой идут запросы, будет заблокирована.</li>
7
<li>Реальные данные на GitHub не статичны, они могут и, скорее всего, будут меняться, что опять же приведет к ошибкам и необходимости править тесты.</li>
7
<li>Реальные данные на GitHub не статичны, они могут и, скорее всего, будут меняться, что опять же приведет к ошибкам и необходимости править тесты.</li>
8
</ol><p>В данном примере HTTP-запрос воспринимается как помеха к тому, чтобы протестировать нашу основную логику. Мы доверяем<em>github.com</em>и его библиотеке<em>KnpLabs/php-github-api</em>, то есть нам не нужно проверять, что она работает правильно (иначе можно свихнуться, если не доверять никому).</p>
8
</ol><p>В данном примере HTTP-запрос воспринимается как помеха к тому, чтобы протестировать нашу основную логику. Мы доверяем<em>github.com</em>и его библиотеке<em>KnpLabs/php-github-api</em>, то есть нам не нужно проверять, что она работает правильно (иначе можно свихнуться, если не доверять никому).</p>
9
<p>Из предыдущего урока мы узнали о нескольких способах выхода из этой ситуации и теперь можем применить один из них.</p>
9
<p>Из предыдущего урока мы узнали о нескольких способах выхода из этой ситуации и теперь можем применить один из них.</p>
10
<h2>Инверсия зависимостей</h2>
10
<h2>Инверсия зависимостей</h2>
11
<p>Для использования инверсии зависимости добавим вторым аргументом функции сам клиент библиотеки. Это позволит подменить его в тестах:</p>
11
<p>Для использования инверсии зависимости добавим вторым аргументом функции сам клиент библиотеки. Это позволит подменить его в тестах:</p>
12
<p>Теперь в тестах можно выполнить подмену реализации клиента. Для этого нам понадобится фейковый объект. Создать подобный объект можно двумя способами: либо описывать полноценный класс, либо использовать встроенный в PHPUnit генератор фейковых объектов (заглушек, stubs). Последний способ является предпочтительным, поэтому разберем его. Пример создания заглушки:</p>
12
<p>Теперь в тестах можно выполнить подмену реализации клиента. Для этого нам понадобится фейковый объект. Создать подобный объект можно двумя способами: либо описывать полноценный класс, либо использовать встроенный в PHPUnit генератор фейковых объектов (заглушек, stubs). Последний способ является предпочтительным, поэтому разберем его. Пример создания заглушки:</p>
13
<p>Метод createMock() создает объект переданного класса или интерфейса, но с некоторыми оговорками. Во время создания, у такого объекта не вызывается конструктор, а все методы становятся пустыми и возвращают null. Такой реализации уже может быть достаточно для подмены.</p>
13
<p>Метод createMock() создает объект переданного класса или интерфейса, но с некоторыми оговорками. Во время создания, у такого объекта не вызывается конструктор, а все методы становятся пустыми и возвращают null. Такой реализации уже может быть достаточно для подмены.</p>
14
<p>Если получившийся объект наполняется дополнительной логикой, то в примере выше к заглушке добавляется метод doSomething(), который всегда возвращает строку<em>foo</em>. createMock() создает заглушки не только на основе классов, но и на основе интерфейсов. Для этого внутри создается временный класс реализующий данный интерфейс. Все это возможно благодаря<a>Reflection Api</a>.</p>
14
<p>Если получившийся объект наполняется дополнительной логикой, то в примере выше к заглушке добавляется метод doSomething(), который всегда возвращает строку<em>foo</em>. createMock() создает заглушки не только на основе классов, но и на основе интерфейсов. Для этого внутри создается временный класс реализующий данный интерфейс. Все это возможно благодаря<a>Reflection Api</a>.</p>
15
<p>Попробуем создать заглушку для тестирования функции getForkedRepositories(). Главная сложность здесь состоит в том, что клиент содержит цепочку из двух вызовов api()->repositories(). PHPUnit позволяет эмулировать и такое поведение, но чуть более сложной конфигурацией:</p>
15
<p>Попробуем создать заглушку для тестирования функции getForkedRepositories(). Главная сложность здесь состоит в том, что клиент содержит цепочку из двух вызовов api()->repositories(). PHPUnit позволяет эмулировать и такое поведение, но чуть более сложной конфигурацией:</p>
16
<p>И сам тест с использованием этой заглушки:</p>
16
<p>И сам тест с использованием этой заглушки:</p>
17
<p>В тестировании для подобных заглушек есть специальное название - стаб (stub). Стаб заменяет реальный объект или функцию, позволяя избежать выполнения побочных эффектов или сделать код детерминированным. Стаб не используется для проверки чего-либо, он лишь позволяет изолировать ту часть, которая "мешает" тестированию основной логики.</p>
17
<p>В тестировании для подобных заглушек есть специальное название - стаб (stub). Стаб заменяет реальный объект или функцию, позволяя избежать выполнения побочных эффектов или сделать код детерминированным. Стаб не используется для проверки чего-либо, он лишь позволяет изолировать ту часть, которая "мешает" тестированию основной логики.</p>