Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Мокирование времени и файлов
mocking_time_files

Мокирование времени и файлов

freezegun для тестов с датами, patch для file I/O. Тесты без побочных эффектов

У вас есть функция calculate_age(birthdate) которая использует datetime.now(). Тест работает сегодня, но сломается через год. Как сделать тест стабильным?

Цель: Научиться мокировать время и файловый ввод-вывод.

Вы точно готовы?

Убедитесь, что умеете:

# Мокировать HTTP с requests-mock import requests_mock def test_api(mock_requests): mock_requests.get("https://api.example.com/data", json={"value": 123})

Если requests-mock непонятен — вернитесь к Мокирование HTTP.

Проблема 1: непредсказуемое время

Код зависящий от времени

# src/user.py from datetime import datetime class User: def __init__(self, name, birthdate): self.name = name self.birthdate = birthdate # "2000-01-15" def get_age(self): """Возвращает возраст пользователя""" today = datetime.now().date() birth = datetime.fromisoformat(self.birthdate).date() age = today.year - birth.year # Корректируем если день рождения ещё не наступил if (today.month, today.day) < (birth.month, birth.day): age -= 1 return age def has_birthday_today(self): """Проверяет день рождения сегодня""" today = datetime.now().date() birth = datetime.fromisoformat(self.birthdate).date() return (today.month, today.day) == (birth.month, birth.day)

Тест который сломается

# ❌ ПЛОХО — тест зависит от даты запуска def test_user_age(): user = User("Alice", "2000-01-15") # Если сегодня 2025-01-16 — возраст 25 # Если сегодня 2025-01-14 — возраст 24 # Тест нестабильный! age = user.get_age() assert age == 25 # Сломается через год или до дня рождения! def test_birthday_today(): user = User("Alice", "2000-01-15") # ❌ Работает только 15 января! assert user.has_birthday_today() == True

Проблемы:

  • ❌ Тесты работают по-разному в зависимости от даты запуска
  • ❌ Невозможно протестировать конкретную дату
  • ❌ Сложно тестировать edge cases (високосные годы, разные часовые пояса)

Решение 1: freezegun

Установка

pip install freezegun

Базовое использование

from freezegun import freeze_time from datetime import datetime @freeze_time("2025-01-15") def test_frozen_time(): """Время 'заморожено' на 15 января 2025""" now = datetime.now() print(now) # 2025-01-15 00:00:00 # Время не меняется! import time time.sleep(1) print(datetime.now()) # Всё ещё 2025-01-15 00:00:00

✅ Тест всегда работает с одной и той же датой!

Тестирование возраста

from freezegun import freeze_time @freeze_time("2025-01-16") # Сегодня 16 января 2025 def test_user_age(): """Возраст пользователя родившегося 15 января 2000""" user = User("Alice", "2000-01-15") age = user.get_age() assert age == 25 # 16 января 2025 — уже 25 лет @freeze_time("2025-01-14") # Сегодня 14 января 2025 (ДО дня рождения) def test_user_age_before_birthday(): """Возраст до дня рождения""" user = User("Alice", "2000-01-15") age = user.get_age() assert age == 24 # День рождения ещё не наступил — 24 года

✅ Тесты стабильны и проверяют разные сценарии!

Тестирование дня рождения

@freeze_time("2025-01-15") def test_birthday_today(): """Проверка дня рождения""" user = User("Alice", "2000-01-15") assert user.has_birthday_today() == True @freeze_time("2025-01-14") def test_not_birthday(): """Не день рождения""" user = User("Alice", "2000-01-15") assert user.has_birthday_today() == False

Контекстный менеджер

from freezegun import freeze_time def test_time_travel(): """Можно 'путешествовать' во времени внутри теста""" with freeze_time("2025-01-01"): assert datetime.now().day == 1 with freeze_time("2025-12-31"): assert datetime.now().day == 31 # Вне блока — реальное время print(datetime.now()) # Реальная дата

Время с часами и минутами

@freeze_time("2025-01-15 10:30:00") def test_specific_time(): """Заморозка конкретного времени""" now = datetime.now() assert now.hour == 10 assert now.minute == 30 assert now.second == 0

Практический пример: SessionManager

# src/session_manager.py from datetime import datetime, timedelta class SessionManager: def __init__(self, timeout_minutes=30): self.timeout_minutes = timeout_minutes self.sessions = {} def create_session(self, user_id): """Создаёт сессию""" session_id = f"session_{user_id}" self.sessions[session_id] = { "user_id": user_id, "created_at": datetime.now(), "expires_at": datetime.now() + timedelta(minutes=self.timeout_minutes) } return session_id def is_valid(self, session_id): """Проверяет валидность сессии""" if session_id not in self.sessions: return False session = self.sessions[session_id] return datetime.now() < session["expires_at"] # tests/test_session_manager.py from freezegun import freeze_time @freeze_time("2025-01-15 10:00:00") def test_create_session(): """Создание сессии""" manager = SessionManager(timeout_minutes=30) session_id = manager.create_session(user_id=1) session = manager.sessions[session_id] assert session["created_at"] == datetime(2025, 1, 15, 10, 0, 0) assert session["expires_at"] == datetime(2025, 1, 15, 10, 30, 0) @freeze_time("2025-01-15 10:00:00") def test_session_valid(): """Сессия валидна сразу после создания""" manager = SessionManager(timeout_minutes=30) session_id = manager.create_session(user_id=1) assert manager.is_valid(session_id) == True @freeze_time("2025-01-15 10:00:00") def test_session_expires(): """Сессия истекает через timeout""" manager = SessionManager(timeout_minutes=30) session_id = manager.create_session(user_id=1) # Перемещаемся на 31 минуту вперёд with freeze_time("2025-01-15 10:31:00"): assert manager.is_valid(session_id) == False @freeze_time("2025-01-15 10:00:00") def test_session_still_valid(): """Сессия валидна за 1 минуту до истечения""" manager = SessionManager(timeout_minutes=30) session_id = manager.create_session(user_id=1) # 29 минут спустя — ещё валидна with freeze_time("2025-01-15 10:29:00"): assert manager.is_valid(session_id) == True

✅ Можем тестировать таймауты БЕЗ ожидания!

Проблема 2: побочные эффекты файлов

Код работающий с файлами

# src/config_reader.py def read_config(filename): """Читает конфигурацию из файла""" with open(filename, "r") as f: content = f.read() return content def save_report(filename, data): """Сохраняет отчёт в файл""" with open(filename, "w") as f: f.write(data) return True

Тест с реальными файлами (плохо)

import os def test_read_config(): # ❌ Создаём реальный файл with open("test_config.txt", "w") as f: f.write("config_value=123") content = read_config("test_config.txt") assert "config_value" in content # Cleanup os.remove("test_config.txt")

Проблемы:

  • ❌ Создаются реальные файлы на диске
  • ❌ Нужен cleanup
  • ❌ Могут быть конфликты (файл уже существует)
  • ❌ Медленнее (I/O операции)

Решение 2: mock_open()

Базовое использование

from unittest.mock import patch, mock_open @patch("builtins.open", mock_open(read_data="config_value=123")) def test_read_config(): """Тест чтения файла БЕЗ реального файла""" content = read_config("any_filename.txt") assert "config_value" in content

✅ Файл НЕ создан, всё работает в памяти!

Проверка записи файла

@patch("builtins.open", mock_open()) def test_save_report(mock_file): """Тест записи файла""" # Act result = save_report("report.txt", "Test data") # Assert assert result == True # Проверяем что open был вызван правильно mock_file.assert_called_once_with("report.txt", "w") # Проверяем что write был вызван с правильными данными handle = mock_file() handle.write.assert_called_once_with("Test data")

Мокирование чтения и записи

# src/file_processor.py def process_file(input_file, output_file): """Читает файл, обрабатывает и записывает результат""" with open(input_file, "r") as f: content = f.read() processed = content.upper() # Простая обработка with open(output_file, "w") as f: f.write(processed) return True # tests/test_file_processor.py from unittest.mock import patch, mock_open, call @patch("builtins.open") def test_process_file(mock_file): """Тест обработки файла""" # Мокируем чтение (input) mock_file.return_value.__enter__.return_value.read.return_value = "hello world" # Act result = process_file("input.txt", "output.txt") # Assert assert result == True # Проверяем что open вызван дважды (чтение + запись) assert mock_file.call_count == 2 # Проверяем первый вызов (чтение) assert mock_file.call_args_list[0] == call("input.txt", "r") # Проверяем второй вызов (запись) assert mock_file.call_args_list[1] == call("output.txt", "w") # Проверяем что записано handle = mock_file() handle.write.assert_called_once_with("HELLO WORLD")

Практический пример: LogManager

# src/log_manager.py from datetime import datetime class LogManager: def __init__(self, log_file): self.log_file = log_file def log(self, level, message): """Записывает лог в файл""" timestamp = datetime.now().isoformat() log_entry = f"[{timestamp}] {level}: {message}\n" with open(self.log_file, "a") as f: f.write(log_entry) def get_logs(self): """Читает все логи""" try: with open(self.log_file, "r") as f: return f.read() except FileNotFoundError: return "" # tests/test_log_manager.py from unittest.mock import patch, mock_open from freezegun import freeze_time @freeze_time("2025-01-15 10:30:00") @patch("builtins.open", mock_open()) def test_log_entry(mock_file): """Тест записи лога""" logger = LogManager("app.log") logger.log("INFO", "Application started") # Проверяем что файл открыт в режиме append mock_file.assert_called_once_with("app.log", "a") # Проверяем содержимое handle = mock_file() handle.write.assert_called_once_with( "[2025-01-15T10:30:00] INFO: Application started\n" ) @patch("builtins.open", mock_open(read_data="[2025-01-15T10:00:00] INFO: Test log\n")) def test_get_logs(mock_file): """Тест чтения логов""" logger = LogManager("app.log") logs = logger.get_logs() assert "Test log" in logs mock_file.assert_called_once_with("app.log", "r") @patch("builtins.open", side_effect=FileNotFoundError) def test_get_logs_file_not_exists(mock_file): """Тест когда файла нет""" logger = LogManager("app.log") logs = logger.get_logs() assert logs == ""

Комбинирование: время + файлы

# src/backup_manager.py from datetime import datetime import json class BackupManager: def create_backup(self, data): """Создаёт резервную копию с timestamp""" timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"backup_{timestamp}.json" with open(filename, "w") as f: json.dump(data, f) return filename # tests/test_backup_manager.py from freezegun import freeze_time from unittest.mock import patch, mock_open @freeze_time("2025-01-15 10:30:00") @patch("builtins.open", mock_open()) def test_create_backup(mock_file): """Тест создания бэкапа""" manager = BackupManager() data = {"users": [1, 2, 3]} filename = manager.create_backup(data) # Проверяем имя файла assert filename == "backup_20250115_103000.json" # Проверяем что файл создан mock_file.assert_called_once_with("backup_20250115_103000.json", "w") # Проверяем что данные записаны handle = mock_file() written_data = handle.write.call_args[0][0] assert "users" in written_data

✅ Тестируем время И файлы БЕЗ побочных эффектов!

Что вы изучили

  • freezegun — мокирование datetime.now()
  • @freeze_time() — фиксация времени в тестах
  • Тестирование дат — возраст, дни рождения, таймауты
  • mock_open() — мокирование file I/O
  • Проверка записи — что записано в файл
  • Проверка чтения — что прочитано из файла
  • Комбинирование — время + файлы в одном тесте

Проверьте свои знания

Вопросы ещё не добавлены

Вопросы для этой подтемы ещё не добавлены.

Далее: Coverage: контроль качества тестов