Состав, тестирование и зависимости в Go

Во многих языках легко заменить заданную функцию или вызов метода фиктивной версией. Динамическая замена или статическое внедрение фиктивных зависимостей значительно упрощает тестирование кода. Лично я нашел этот подход далеко не идеальным в Go, поэтому я искал другой способ достичь некоторых целей мокинга и внедрения зависимостей. Один из таких подходов заключается в использовании композиции функций, которая имеет другие компромиссы по сравнению с внедрением зависимостей. После прочтения Mocking is a Code Smell меня вдохновило попробовать это в Go. Я настоятельно рекомендую его как подробное объяснение состава функций в этом приложении.

Предположение, которое я делаю для простого примера, заключается в том, что не существует активной библиотеки стилей записи. Поскольку пример сосредоточен вокруг взаимодействия с БД, способ реализации и написания тестов для следующих функций будет совершенно другим в зависимости от использования библиотеки такого типа.

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

Во всех случаях потребуется интеграционный или функциональный тест, чтобы подтвердить правильность запроса или обновления. Существует несколько возможных запросов/обновлений, которые приведут к одному и тому же результату, поэтому достаточно только интеграционного теста. Например, тест не должен провалиться, если предоставлена ​​дополнительная опция.

Базовый код

Это базовый прямой код, напрямую использующий библиотеки db. Код понятен, но также сильно связан со своими зависимостями. Побочные эффекты не отделены от логики, и никакая зависимость не внедряется, поэтому ничто не может быть сымитировано. Для проверки этого требуется тестовый экземпляр БД. В результате тесты медленнее и более подвержены уязвимости, и с таким кодом часто в конечном итоге будут тесты только для счастливого пути.

Извлеченные функции

Один из подходов к рефакторингу заключается в извлечении закрытых функций. Возможно, его стало немного легче читать, но это совсем не упростило тестирование. Если бы обработка ошибок была более обширной, то ее также можно было бы частично абстрагировать в подфункциях.

Интерфейсы

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

Функциональная композиция

Если публичная функция просто составляет другие функции, мы можем отделить побочные эффекты от чистых функций. Если бы в Go были дженерики, то и сама композиция могла бы быть написана дженериками. Но даже в этом случае компилятор достаточно настойчив, так что, вероятно, не стоит затрачивать усилия на непосредственное тестирование самой общедоступной функции.

Адаптеры изменяют интерфейс существующей библиотеки на более удобный интерфейс для нашего конкретного случая использования. Извлечение зависимостей для функций позволяет легко тестировать их по отдельности.

Тестирование приватной функции так же просто, как передача чистых функций или замыканий. Вместо того, чтобы подгонять тест под любой интерфейс API вашей библиотеки, вы создаете идеальный интерфейс для своей цели и делаете так, чтобы ваши зависимости соответствовали ему. Это можно легко протестировать, потому что логика может быть изолирована как от побочных эффектов, так и от деталей реализации. В результате я нахожу версию зависимостей функций гораздо более читабельной, чем другие версии.

Настроить тестирование для версии зависимости функции очень просто, так как не требуются специальные библиотеки. Однако клиент должен быть заглушен.

Тестирование адаптеров также очень просто по тем же причинам, что и выше.

Вывод

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