Как превратить недетерминированный код в красивый детерминированный набор модульных тестов

Вы написали очень красивую бизнес-логику, которая зависит от текущего времени. Теперь вы хотите проверить это.

Одним из важных свойств тестов является их детерминированность: каждый раз, когда вы запускаете тесты, они должны давать один и тот же результат, если только вы не изменили саму логику.

Когда ваша логика зависит от чего-то вроде time.Now(), это означает, что она зависит от текущего времени машины, на которой вы выполняете свой код. Другими словами, при тестировании этого фрагмента кода вы получите разные результаты в зависимости от времени запуска теста. Это нежелательно!

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

Я поместил код этой статьи в свой репозиторий GitHub, чтобы вам было еще проще опробовать то, что я написал здесь: https://github.com/matteo- pampana/статья-тест-время

Давайте посмотрим на пример в действии

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

Хорошо, теперь мы хотим протестировать этот код.

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

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

Первое, что может прийти вам в голову, это посмотреть часы и установить время создания последнего элемента менее чем на 10 секунд до времени, которое вы только что просмотрели. Ну удачи. Если вам повезет, ваш тест может запуститься, как вы ожидали, максимум один раз. 🔴

Итак, что мы можем сделать, чтобы полностью контролировать наши тесты?

Вы хозяин (испытательного) времени

Решение состоит в том, чтобы освоить время тестов. Только в вашем тесте — иначе вы нарушите поведение своего метода — вы должны имитировать функцию time.Now и заставить ее возвращать константу time.Time по вашему выбору.

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

Код будет выглядеть так:

Реализация не сильно изменилась. Как видите, я добавил в структуру Service специальное поле с именем now, которое представляет собой функцию, возвращающую объект time.Time. Благодаря «утиному набору» — если он выглядит как утка и крякает как утка, значит, это утка — вы можете назначить поле now для функция time.Now внутри NewService. Это гарантирует, что вызов s.now() внутри метода CreateItem функционально идентичен тому, который мы написали ранее. Хороший!

В этом случае я предпочитаю не экспортировать это конкретное поле структуры Service. Таким образом, другие пакеты, когда они импортируют эту Службу, не могут непреднамеренно изменить функцию now, используемую внутри модуля.

Но благодаря этому полю и тому факту, что тест находится в том же пакете, что и ваша логика, теперь вы можете получить доступ к неэкспортированному полю now (каламбур) и установить его по своему усмотрению: Вы хозяин (тестового) времени.

Давайте посмотрим на пример тестового примера для этой функции:

Как видите, в строке 31 я установил поле s.now с time.Time, указанным в моих тестовых примерах в строке 17. Затем в строке 13, в макете, я установил ожидания времени создания последнего элемента. testTime составляет 9 секунд после времени создания последнего элемента, поэтому наша логика должна возвращать ошибку errTooFast. Вы выполняете тест, вы проходите тест. Ура! 🚀

Выводы

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

Вы можете протестировать зависящую от времени логику, выполнив следующие три шага:

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

Нажмите на кнопку аплодисментов, если вы считаете эту статью полезной.

Оставляйте комментарии, ваше мнение очень ценно ❤️