Два способа тестирования функций, взаимодействующих с операционной системой

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

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

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

Тестирование с реальной операционной системой

Некоторым программам необходимо взаимодействовать с компонентами или артефактами операционной системы, например:

  • Переменные среды
  • Взаимодействие с файловой системой.
  • Локальная сеть

Выполнение тестов в среде операционной системы может вызвать дополнительные проблемы, такие как:

  • Проблемы с разрешениями.
  • Проблемы с безопасностью
  • Настройка сложной среды.
  • Необходимость очистки всего в конце.

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

Первая стратегия: работа с абстракциями

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

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

Мы можем сформировать следующий интерфейс для взаимодействия с файловой системой:

type FSReader interface {
  ReadFile(string) ([]byte, error)
}

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

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

Вторая стратегия: заглушки

Существует альтернатива использованию макетов и абстракций, известных как заглушки.

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

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

В Go такой функционал предоставляет пакет gostub.

В приведенном выше примере вывод функции MultiplyBy10 был заменен функцией-заглушкой, где она возвращает ввод, умноженный на 20, а не на 10, как было написано вначале.

Мы также можем заглушить переменные среды, если мы их используем:

func TestFunctionThatUsesENV(t *testing.T) {
  stubs := gostub.New()
  stubs.SetEnv("ENV_VAR", "some_value")
  defer stubs.Reset()
  
  // Some Logic that Uses ENV_VAR
  // ...
}

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

Для набора модульных тестов мы могли бы заглушить функцию и восстановить ее в конце:

Другие способы использования заглушек или макетов

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

Заключение и основные выводы

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

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

Я надеюсь, что эти советы были полезны! Спасибо за прочтение.