Представьте функцию, проверяющую срок действия объекта. Тест должен надёжно проверить поведение ровно в момент истечения срока. Как изменить границу функции, чтобы результат не зависел от системных часов?
from datetime import datetime, timezone
def is_expired(expires_at):
return datetime.now(timezone.utc) >= expires_at
Нужно убрать прямую зависимость функции от текущего времени и передавать источник времени извне. Такой приём называется внедрением зависимости и повышает тестируемость: тест получает контролируемое время и становится детерминированным.
Не следует проверять этот случай через sleep: задержка делает тест медленным и не гарантирует попадание точно в нужный момент.
По мере роста систем тесты стали зависеть не только от входных параметров, но и от времени, случайности, файловой системы, сети и других внешних ресурсов. Такие зависимости затрудняют воспроизводимость проверок и увеличивают стоимость диагностики отказов.
Для изоляции логики применяют передачу зависимостей через параметры, конструкторы или специальные интерфейсы. Время — частный случай такой зависимости: бизнес-правилу нужен момент, но не обязательно реальный системный clock.
В исходной функции datetime.now(...) вызывается внутри проверяемой логики. Тест не управляет этим значением: между вычислением ожидаемого результата и вызовом функции время может измениться на границе срока.
Попытка дождаться нужного момента через sleep создаёт хрупкий тест. На загруженной машине он может проснуться раньше или позже, а при параллельном запуске поведение станет ещё менее предсказуемым.
Передайте функцию получения времени как зависимость:
В рабочем коде clock возвращает текущее время, а в тесте — заранее заданное значение. Поэтому можно отдельно проверить момент до границы, саму границу и момент после неё без ожидания и влияния системных часов.
Оператор >= здесь также является частью контракта: он означает, что срок считается истёкшим ровно в момент expires_at. Если бизнес-правило требует другой семантики, это нужно явно зафиксировать в требовании и тестах.
При внедрении времени важно использовать единый часовой пояс, обычно UTC, и сопоставимые типы дат. Подмена часов глобальным monkey patch допустима как инструмент legacy-тестирования, но обычно хуже явной зависимости: она скрывает связь функции с временем и может влиять на другие тесты.
В сервисе подписок тесты периодически падали около полуночи: один тест создавал подписку со сроком действия «сейчас», а другой проверял её состояние после небольшой задержки. Вариант с увеличением таймаутов устранял часть сбоев, но делал набор медленнее и не решал проблему полностью.
Вариант с реальными часами и sleep был простым, но зависел от нагрузки на агент CI. Глобальная подмена системного времени давала контроль, однако могла затронуть сторонние компоненты и усложняла параллельный запуск.
Команда ввела объект или функцию Clock, передаваемую в сервис через конструктор. В боевом окружении использовались реальные часы, а в модульных тестах — фиксированные или управляемые. В результате проверки границ стали быстрыми и воспроизводимыми, а правила истечения срока можно было проверять независимо от календарного времени.
Почему передача фиксированного времени лучше проверки с задержкой?
sleep проверяет не бизнес-границу, а поведение системы через приблизительный промежуток времени. Результат зависит от планировщика, нагрузки и точности таймера. Фиксированное время позволяет непосредственно задать условие теста и получить стабильный результат.
Достаточно ли подменить часы, если даты находятся в разных часовых поясах?
Нет. Подмена источника времени устраняет недетерминизм, но не исправляет ошибки преобразования дат. Нужно согласовать формат и часовой пояс: например, сравнивать timezone-aware значения в UTC. Отдельные тесты должны проверять преобразование пользовательского локального времени в момент, используемый бизнес-логикой.
Можно ли передавать время обычным параметром вместо отдельного clock-объекта?
Можно, если функция действительно выполняет одну операцию в переданный момент и не получает время в нескольких местах. Для сложного сервиса отдельный Clock удобнее: он централизует получение времени, позволяет управлять им в тестах и уменьшает риск, что часть кода случайно вызовет системные часы напрямую. Выбор зависит от размера компонента и количества операций со временем.