Тесты, зависящие от времени: приведите примеры «flaky tests», которые ломаются из‑за времени/локали/порядка выполнения, и опишите набор практик (даже инструментов), позволяющих сделать тесты детерминированными
Примеры flaky tests (ломаются из‑за времени / локали / порядка выполнения) и почему: - Тайм‑зависимые (time) - Тест проверяет «сегодняшнюю дату» или относительные даты, например: ожидает, что функция вернёт now+1 day \text{now} + 1\ \text{day} now+1day. При прогоне в полночь/переходе на летнее время — падает. - Тест использует реальные таймеры и явные задержки: проверка через `sleep(100)` или `assert after 100ms` — на медленной CI машинах артефакты. - Тест зависит от системного времени/часового пояса: код форматирует время в локальном TZ и тест ожидает конкретную строку. - Локализация (locale) - Форматирование чисел и дат: в одной среде десятичный разделитель «.» в другой «,» — тест сравнивает строковую репрезентацию. - Сортировка/сравнение строк (collation) зависит от локали — порядок элементов меняется. - Валюта/локализованные сообщения: тест ожидает «\$100.00», а система отдает «100,00 ₽». - Порядок выполнения / глобальное состояние - Тест A очищает глобальный синглтон/кеш, тест B ожидает его заполненным — при смене порядка B падает. - Общая база данных/файловая система: тесты не изолированы, зависимости от предыдущих данных. - Параллельное выполнение: гонки данных, незащищённые разделяемые ресурсы, случайные порты (конфликт на 808080808080). - Случайности и внешние ресурсы - Тест использует нерегулируемый генератор случайных чисел — иногда встречаются редкие случайности. - Зависимость от сети/внешних API: временные сбои, задержки, throttling. Практики и инструменты, чтобы сделать тесты детерминированными - Управление временем - Инъекция часов (dependency injection): в коде использовать абстракцию Clock/TimeSource вместо вызова глобального времени. В тесте подставлять фейковый/фиксированный часовой источник. - Заморозка времени: freezegun (Python), Timecop (Ruby), Sinon/lolex или Jest fake timers (JS). Пример: в тесте установить фиксированное now \text{now} now и ожидать предсказуемый результат. - Избегать `sleep`; вместо этого использовать polling с таймаутом и явными ассертами или мок асинхронной логики. - Контроль локали/кодировок - В тестовой среде явно устанавливать локаль и кодировку (например, `LANG=ru_RU.UTF-8`) перед запуском тестов. - Тестировать форматирование через API, принимающее локаль как параметр, или мокать глобальную локаль. - Для строковых сравнений использовать нормализацию (Unicode NFC) и сравнивать семантику, а не точную строку, если это релевантно. - Изоляция и чистота окружения - Каждый тест — независимая фикстура: setup/teardown очищают БД, кеши, файлы. Использовать транзакции с откатом по окончании теста. - Тестовые данные — фиксированные, контроль над генерацией идентификаторов (не использовать глобальные счётчики без сброса). - Использовать контейнеры / Testcontainers / docker-compose для изоляции внешних сервисов (БД, брокеры). Это делает окружение повторяемым. - Управление порядком и параллелизмом - Сбрасывать/инициализировать глобальное состояние между тестами; избегать зависимостей от порядка. - Запускать тесты в рандомизированном порядке в CI (pytest-randomly, junit-плагины, mocha — randomize) и фиксировать seed: если тест ломается при случайном порядке — отловить и исправить. - Тестировать параллельно с отключёнными общими ресурсами или обеспечить синхронизацию/локи. - Детeрминизм генераторов и свойств - Задавать фиксированный seed для случайных генераторов в тестах и для property‑testing (Hypothesis, QuickCheck) — при провале можно воспроизвести. - Для асинхронных/параллельных сценариев использовать контролируемые планировщики/фейковые таймеры. - Моки и контроли внешних зависимостей - Мокать сетевые вызовы и внешние API; для интеграционных тестов использовать стабильные тестовые стенды или записанные ответы (VCR, betamax). - Ограничивать время ожидания и делать тесты устойчивыми к временным ошибкам: экспоненциальные/контролируемые retries с фиксированными параметрами для тестовой среды. - Инструменты и конфигурация CI - Зафиксировать окружение в CI (образ Docker, системные локали, TZ), логировать seed и время при падении. - Запускать «flaky detection»: многократные прогоны (например, repeat=10 \text{repeat} = 10 repeat=10) для выявления нестабильных тестов; инструменты: pytest-xdist + pytest-rerunfailures, CI повторные запуски. - Использовать Testcontainers для воспроизводимой конфигурации сервисов. Короткие практические рекомендации (чеклист) - Используй инъекцию времени и фейковые таймеры. - Всегда сбрасывай глобальное состояние между тестами. - Зафиксируй локаль и TZ в тестовой среде. - Не полагайся на `sleep`; используй polling/await. - Фиксируй seed для RNG и property‑tests. - Изолируй внешние сервисы в контейнерах или моки. - Рандомизируй порядок тестов в CI и фиксируй seed при падении для отладки. Если нужно, могу привести конкретные примеры кода для языка (Python/JS/Java/Ruby) по внедрению фейковых часов, фиксации локали или конфигурации тестового окружения.
- Тайм‑зависимые (time)
- Тест проверяет «сегодняшнюю дату» или относительные даты, например: ожидает, что функция вернёт now+1 day \text{now} + 1\ \text{day} now+1 day. При прогоне в полночь/переходе на летнее время — падает.
- Тест использует реальные таймеры и явные задержки: проверка через `sleep(100)` или `assert after 100ms` — на медленной CI машинах артефакты.
- Тест зависит от системного времени/часового пояса: код форматирует время в локальном TZ и тест ожидает конкретную строку.
- Локализация (locale)
- Форматирование чисел и дат: в одной среде десятичный разделитель «.» в другой «,» — тест сравнивает строковую репрезентацию.
- Сортировка/сравнение строк (collation) зависит от локали — порядок элементов меняется.
- Валюта/локализованные сообщения: тест ожидает «\$100.00», а система отдает «100,00 ₽».
- Порядок выполнения / глобальное состояние
- Тест A очищает глобальный синглтон/кеш, тест B ожидает его заполненным — при смене порядка B падает.
- Общая база данных/файловая система: тесты не изолированы, зависимости от предыдущих данных.
- Параллельное выполнение: гонки данных, незащищённые разделяемые ресурсы, случайные порты (конфликт на 808080808080).
- Случайности и внешние ресурсы
- Тест использует нерегулируемый генератор случайных чисел — иногда встречаются редкие случайности.
- Зависимость от сети/внешних API: временные сбои, задержки, throttling.
Практики и инструменты, чтобы сделать тесты детерминированными
- Управление временем
- Инъекция часов (dependency injection): в коде использовать абстракцию Clock/TimeSource вместо вызова глобального времени. В тесте подставлять фейковый/фиксированный часовой источник.
- Заморозка времени: freezegun (Python), Timecop (Ruby), Sinon/lolex или Jest fake timers (JS). Пример: в тесте установить фиксированное now \text{now} now и ожидать предсказуемый результат.
- Избегать `sleep`; вместо этого использовать polling с таймаутом и явными ассертами или мок асинхронной логики.
- Контроль локали/кодировок
- В тестовой среде явно устанавливать локаль и кодировку (например, `LANG=ru_RU.UTF-8`) перед запуском тестов.
- Тестировать форматирование через API, принимающее локаль как параметр, или мокать глобальную локаль.
- Для строковых сравнений использовать нормализацию (Unicode NFC) и сравнивать семантику, а не точную строку, если это релевантно.
- Изоляция и чистота окружения
- Каждый тест — независимая фикстура: setup/teardown очищают БД, кеши, файлы. Использовать транзакции с откатом по окончании теста.
- Тестовые данные — фиксированные, контроль над генерацией идентификаторов (не использовать глобальные счётчики без сброса).
- Использовать контейнеры / Testcontainers / docker-compose для изоляции внешних сервисов (БД, брокеры). Это делает окружение повторяемым.
- Управление порядком и параллелизмом
- Сбрасывать/инициализировать глобальное состояние между тестами; избегать зависимостей от порядка.
- Запускать тесты в рандомизированном порядке в CI (pytest-randomly, junit-плагины, mocha — randomize) и фиксировать seed: если тест ломается при случайном порядке — отловить и исправить.
- Тестировать параллельно с отключёнными общими ресурсами или обеспечить синхронизацию/локи.
- Детeрминизм генераторов и свойств
- Задавать фиксированный seed для случайных генераторов в тестах и для property‑testing (Hypothesis, QuickCheck) — при провале можно воспроизвести.
- Для асинхронных/параллельных сценариев использовать контролируемые планировщики/фейковые таймеры.
- Моки и контроли внешних зависимостей
- Мокать сетевые вызовы и внешние API; для интеграционных тестов использовать стабильные тестовые стенды или записанные ответы (VCR, betamax).
- Ограничивать время ожидания и делать тесты устойчивыми к временным ошибкам: экспоненциальные/контролируемые retries с фиксированными параметрами для тестовой среды.
- Инструменты и конфигурация CI
- Зафиксировать окружение в CI (образ Docker, системные локали, TZ), логировать seed и время при падении.
- Запускать «flaky detection»: многократные прогоны (например, repeat=10 \text{repeat} = 10 repeat=10) для выявления нестабильных тестов; инструменты: pytest-xdist + pytest-rerunfailures, CI повторные запуски.
- Использовать Testcontainers для воспроизводимой конфигурации сервисов.
Короткие практические рекомендации (чеклист)
- Используй инъекцию времени и фейковые таймеры.
- Всегда сбрасывай глобальное состояние между тестами.
- Зафиксируй локаль и TZ в тестовой среде.
- Не полагайся на `sleep`; используй polling/await.
- Фиксируй seed для RNG и property‑tests.
- Изолируй внешние сервисы в контейнерах или моки.
- Рандомизируй порядок тестов в CI и фиксируй seed при падении для отладки.
Если нужно, могу привести конкретные примеры кода для языка (Python/JS/Java/Ruby) по внедрению фейковых часов, фиксации локали или конфигурации тестового окружения.