Mock'ирование datetime.date.today() в Python: решение проблемы

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Для мока функции datetime.date.today(), используйте метод unittest.mock.patch непосредственно в классе date, который импортирован в модуле, подвергающемся тестированию. Вот пример применения:

Python
Скопировать код
from unittest.mock import patch
from datetime import date
import my_module

with patch('my_module.date.today') as mock_today:
    mock_today.return_value = date(2023, 1, 1)
    # После замены функции datetime.date.today() my_module ‘считает’, что сегодня 1 января 2023 года

В этом контексте, вместо запроса реального текущего времени функция my_module.date.today() будет возвращать заданный объект date, обеспечивая тем самым предсказуемость тестов.

Кинга Идем в IT: пошаговый план для смены профессии

Углубляемся в использование моков

unittest.mock.patch все мы знаем и любим, но есть и другая отличная библиотека, заслуживающая вашего внимания: freezegun.

Расслабляемся с freezegun 🧊⏲️

Основная задача, которую решает Freezegun, — это управление временем. Для установки библиотеки выполните pip install freezegun. Декоратор @freeze_time("ГГГГ-ММ-ДД") позволяет провести путешествие во времени:

Python
Скопировать код
from freezegun import freeze_time
import my_module

@freeze_time("2023-01-01")
def test_something():
    assert my_module.some_function() == expected_result  # Теперь мы можем проверять функции, которые зависят от времени

Freezegun заставляет время остановиться, фиксируя его значение во всех модулях, что очень полезно при тестировании.

MagicMock: коллега Patch

Когда работа идёт с изменяющимися датами, unittest.mock.MagicMock сможет добавить ваших тестам немного магии:

Python
Скопировать код
from unittest.mock import patch, MagicMock
from datetime import datetime, timedelta

with patch('my_module.date.today', new_callable=MagicMock) as mock_today:
    mock_today.side_effect = [datetime.today(), datetime.today() + timedelta(days=1)]
    # Сегодня функция вернёт текущую дату, а завтра — дату, смещённую на один день вперёд

Ловушки, на которые следует быть внимательным

Необходимо аккуратно обращаться с импортами:

Мокируем импорты: Заменяйте на мок функцию date.today() в том модуле, где она используется.

Markdown
Скопировать код
- ✅ Верно: Мокируем `'my_app.my_module.date.today'` 
- ❌ Не верно: Мокируем `'datetime.date.today'`

Контролируем выполнение: Проверяйте результаты, чтобы убедиться, что мок работает как задумано.

Ограничиваем область применения моков: Используйте @patch('your_module.datetime'), чтобы корректно ограничить действие мока и избежать его проникновения за пределы тест-кейсов.

Визуализация

Представим, что вы управляете отображением времени в большом городе:

Markdown
Скопировать код
| Действительное время  | Метод                   | Имитируемое время   |
| 📅 **Сегодняшняя дата**| Применение `mock`       | 🔄 **Мокируемая дата** |

Вам потребуются правильные инструменты для изменения этого отображения — в данном случае это unittest.mock:

Markdown
Скопировать код
- 🛠️ **Инструмент**: `mock` из модуля `unittest`
- 📺 **Цель**: `datetime.date.today()`
- ❌ **Проблема**: Дата на экране не меняется

И тогда, когда у вас появится подходящий код:

Python
Скопировать код
with mock.patch('my_app.my_module.date.today') as mock_date:
    mock_date.return_value = date(2022, 1, 1)  # Мы перемещаемся в прошлое и меняем отображаемую дату
    assert my_module.function_display_date() == '01 января 2022'
Markdown
Скопировать код
**Результат**: 📅 Экран показывает другую, замоканную дату! Вам не придётся использовать конденсатор потока.

Не забывайте о важности правильного способа импортирования при использовании mock.

Мокайте как профи: продвинутые советы

Создаем всесторонние моки для взаимосвязанных модулей

Если модули имеют связь, использование Freezegun поможет "заморозить" время сразу для всех модулей.

Мокирование обёрток

Для сохранения оригинального поведения других функций datetime следует использовать аргумент wraps:

Python
Скопировать код
from unittest.mock import patch, MagicMock
from datetime import datetime

def custom_today():
    return datetime(2023, 1, 1)

with patch('my_module.datetime', wraps=datetime) as mock_dt:
    mock_dt.date.today = MagicMock(side_effect=custom_today)
    # Это изменение затронет только сегодняшнюю дату

Тестирование крайних случаев

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

Полезные материалы

  1. unittest.mock — библиотека для работы с мок-объектами — Документация Python 3.12.2 — официальное руководство по мокированию.
  2. Понимание использования мок-объектов в Python – Real Python — подробное объяснение работы с моками.
  3. Мок-объекты в Python: как улучшить юнит-тесты | Toptal® — советы по повышению эффективности юнит-тестирования.
  4. python – Пытаюсь замокать datetime.date.today(), но не получается – Stack Overflow — обсуждение и решение проблемы с мокированием datetime.date.today().
  5. [Тестирование кода на Python с помощью pytest [Книга]](https://www.oreilly.com/library/view/python-testing-with/9781680502848/) — книга про тестирование при помощи pytest.