Я ходил кругами, пытаясь найти лучший способ модульного тестирования клиентской библиотеки API, которую я разрабатываю. Библиотека имеет Client
класс, который в основном имеет отображение 1: 1 с API, и дополнительный Wrapper
класс, который обеспечивает более удобный интерфейс поверх Client
.
Wrapper --> Client --> External API
Сначала я написал несколько тестов для обоих Client
и Wrapper
, фактически, просто для проверки того, что они перенаправляют к соответствующим функциям того, над чем работают ( Wrapper
работают Client
и Client
работают по HTTP-соединению). Однако мне стало не по себе от этого, потому что я чувствую, что тестирую реализацию этих классов, а не интерфейс. Теоретически, я мог бы изменить классы, чтобы иметь другую совершенно правильную реализацию, но мои тесты не пройдут, потому что функции, которые я ожидал вызвать, не вызываются. Это звучит как хрупкие испытания для меня.
После этого я подумал об интерфейсе занятий. Тесты должны проверить, что классы действительно выполняют ту работу, для которой они предназначены, а не то, как они это делают. Так как я могу это сделать? Первое, что приходит на ум, - это заглушка запросов внешнего API. Однако я нервничаю по поводу упрощения внешнего сервиса. Множество примеров API-заглушек, которые я видел, просто дают постоянные ответы, что звучит как очень простой способ проверить, что ваш код работает правильно против вашего поддельного API. Альтернатива - издеваться над сервисом, который просто невозможен, и его необходимо постоянно обновлять всякий раз, когда реальный сервис меняется - это похоже на излишнее количество и трата времени.
Наконец, я прочитал это из другого ответа на программистов SE :
Задача удаленного API-клиента - выполнять определенные вызовы - не больше и не меньше. Поэтому его тест должен проверить, что он выполняет эти вызовы - ни больше, ни меньше.
И теперь я более или менее убежден - при тестировании Client
все, что мне нужно для тестирования, это то, что он делает правильные запросы к API (Конечно, всегда есть вероятность, что API изменится, но мои тесты продолжают проходить - но это где интеграционные тесты пригодятся). Так как Client
это всего лишь отображение 1: 1 с API, мое беспокойство перед переходом от одной допустимой реализации к другой на самом деле не применимо - есть только одна действительная реализация для каждого метода Client
.
Тем не менее, я все еще застрял в Wrapper
классе. Я вижу следующие варианты:
Я заглушаю
Client
класс и просто проверяю, вызваны ли соответствующие методы. Таким образом, я делаю то же самое, что и выше, но рассматриваю егоClient
как замену API. Это возвращает меня туда, откуда я начал. Еще раз, это дает мне неприятное ощущение от тестирования реализации, а не интерфейса. ЭтоWrapper
вполне может быть реализовано с использованием совершенно другого клиента.Я создаю макет
Client
. Теперь я должен решить, как далеко зайти с его издевательством - создание полного макета сервиса потребует больших усилий (больше работы, чем ушло в саму библиотеку). Сам API прост, но сервис довольно сложен (по сути, это хранилище данных с операциями над этими данными). И опять же, мне придется держать мой макет синхронно с реальнымClient
.Я просто проверяю, что выполняются соответствующие HTTP-запросы. Это означает, что для выполнения этих HTTP-запросов
Wrapper
будет вызываться реальныйClient
объект, поэтому я не проверяю его в отдельности. Это делает его немного ужасным модульным тестом.
Так что я не особенно доволен любым из этих решений. Что бы вы сделали? Есть ли правильный способ пойти по этому поводу?