Ненадежные тесты: Что их вызывает и как их исправить
Тест, который проваливается случайно, учит вашу команду игнорировать ошибки. Узнайте, как подтвердить, что тест ненадежен, найти настоящую причину и исправить ее с помощью демонстраций, показывающих, что на самом деле работает.
- Ненадежный тест проходит и не проходит для одного и того же кода без изменений.
- Обычными причинами являются время, общее состояние, нестабильные данные, внешние службы и различия в среде.
- Замените фиксированные периоды ожидания ожиданием определенного условия и дайте каждому тесту свои собственные данные.
- Повторные попытки могут скрыть настоящие ошибки. Поместите ненадежный тест в карантин, исправьте его в установленный срок или удалите.
Сборка становится красной, кто-то нажимает «перезапустить», она проходит, и все идут дальше. Это кажется безобидным, но ненадежные тесты постепенно разрушают самое ценное, что есть в наборе тестов: доверие к нему людей. Когда сбои обычно являются шумом, возникают и настоящие ошибки.
В этом руководстве рассказывается, почему тесты становятся нестабильными и как правильно устранить каждую причину. Мы запустили демонстрации синхронизации и общего состояния в Python 3.13, и показанные цифры взяты из этих запусков. Это небольшие демонстрации, предназначенные для того, чтобы было легче увидеть недостатки, поэтому ваша собственная частота неудач будет отличаться.
Что такое слоеный тест?
Ненадежный тест дает разные результаты для одного и того же кода. Он проходит при одном запуске и терпит неудачу при следующем, хотя в продукте ничего не изменилось. Возможно, тест ошибочен, или код может содержать реальную периодически возникающую ошибку. Именно из-за этой второй возможности вам не следует просто отмахиваться от них.
Почему ненадежные тесты стоят так дорого
- Потеря доверия. Как только люди начинают считать, что красный означает «вероятно ненадежный», они перестают расследовать реальные неудачи.
- Пустая трата времени. Разработчики часами перезапускают конвейеры и исследуют сбои, не имеющие ничего общего с их изменениями.
- Медленная доставка. Слияния и релизы ждут повторных запусков, что подрывает суть CI/CD.
- Скрытые ошибки. Условия гонки в продукте могут выглядеть так же, как тестовый шум, и именно они мешают работе.
Общие причины с первого взгляда
| Причина | Типичный знак | Обычное исправление |
|---|---|---|
| Тайминг и асинхронность | Больше сбоев, когда CI занят | Подождите условия |
| Общее состояние | Сбой в зависимости от порядка тестирования | Изолировать данные, сбросить состояние |
| Даты и случайность | Сбой в определенное время или дни | Управляйте часами и семенами |
| Хрупкие селекторы | Сбой после изменения пользовательского интерфейса | Используйте стабильные локаторы |
| Внешние услуги | Сбой, когда зависимость медленная | Высмеивайте или заглушайте их |
| Различия в окружающей среде | Проходит локально, не проходит в CI | Совместить с контейнерами |
| Ограничения ресурсов | Сбой при параллельных запусках | Настройте рабочих, устраните конфликты |
Причина 1: тайминг и фиксированный сон
Это наиболее распространенная причина. Тест предполагает, что фоновая работа будет завершена после фиксированной задержки. Чтобы увидеть эффект, мы написали небольшой тест, в котором работа занимает до 2 мс, при этом тест перед проверкой ждет всего 1 мс:
def flaky_timing():
result = {}
def work():
time.sleep(random.uniform(0, 0.002))
result["done"] = True
threading.Thread(target=work).start()
time.sleep(0.001) # hope it has finished
assert result.get("done")
Решение состоит в том, чтобы дождаться фактического состояния с большим верхним пределом:
def fixed_timing():
result = threading.Event()
def work():
time.sleep(random.uniform(0, 0.002))
result.set()
threading.Thread(target=work).start()
assert result.wait(timeout=1) # continues as soon as it is ready
Мы запускали каждую версию 200 раз:
| Версия | Неудачи из 200 |
|---|---|
| Фиксированный сон | 102 |
| Подождите условия | 0 |
Результат – больше, чем просто надежность. Фиксированная версия продолжает работать в момент завершения работы, поэтому обычно она также быстрее. Более длительный сон приведет к тому, что первая версия будет выходить из строя реже, но тогда каждый запуск будет медленнее, и она все равно может выйти из строя на загруженной машине CI.
В браузерных тестах применяется тот же принцип. Используйте автоматическое ожидание Playwright или явное ожидание Selenium вместо time.sleep:
# Playwright: retries until true or a timeout is reached
expect(page.get_by_text("Order placed")).to_be_visible()
# Selenium: an explicit wait for a condition
WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "confirmation"))
)
Причина 2: общее состояние и порядок тестирования.
Когда тесты совместно используют данные, один может повлиять на другой, и результат зависит от порядка их выполнения. Эти два теста используют один общий список:
store = []
def test_add():
store.append("x")
assert len(store) == 1
def test_empty():
assert store == []
| Заказать | test_add | test_empty |
|---|---|---|
| пусто, затем добавьте | Пройти | Пройти |
| добавить, затем пусто | Пройти | Неудача |
Наш запуск подтвердил это, и именно поэтому пакет может работать месяцами, а затем ломаться, когда кто-то добавляет параллельные запуски или плагин случайного порядка. Лечение – изоляция:
- Предоставляйте каждому тесту свежие данные, используя приспособление, которое настраивает и очищает их. В нашем руководстве по pytest показано, как это сделать.
- Создавайте уникальные записи, например электронное письмо со случайным суффиксом, вместо общего «тестового пользователя».
- Оберните тесты базы данных в транзакцию и откатите ее назад, если это позволяют ваши настройки.
- Регулярно запускайте тесты в случайном порядке, чтобы эти проблемы проявлялись раньше.
Причина 3: Даты, часовые пояса и случайность
Тест, который проходит всю неделю, может не пройти в субботу, в полночь или в последний день месяца. Сделайте время входными данными, а не считывайте часы внутри своей логики:
def is_weekend(today=None):
today = today or datetime.date.today()
return today.weekday() >= 5
is_weekend(datetime.date(2026, 11, 7)) # True (a Saturday)
is_weekend(datetime.date(2026, 11, 9)) # False (a Monday)
Эти тесты дают один и тот же ответ в любой день. Аналогичным образом установите фиксированное начальное значение для случайных значений в тестах и регистрируйте его при использовании случайных данных, чтобы можно было воспроизвести сбой. Не предполагайте определенный порядок наборов, словарей из внешних источников или запросов к базе данных без ORDER BY.
Причина 4: хрупкие селекторы
Тест, который находит кнопку с div:nth-child(3) > span, не работает при любой настройке макета и может завершиться неудачно только для некоторых размеров или вариантов экрана. Отдавайте предпочтение локаторам, основанным на том, что видят пользователи, или специальным атрибутам тестирования. В нашем руководстве по локаторам драматургов объясняется порядок их использования. Анимация — еще один частый источник проблем, поэтому рассмотрите возможность ее отключения в тестовых средах.
Причина 5. Внешние службы и сети.
Если тест вызывает реального платежного провайдера или сторонний API, то его результат зависит от чужого времени безотказной работы и ограничений скорости. Имитируйте эти вызовы для большинства тестов и сохраняйте небольшое количество отдельных проверок контракта с реальным сервисом, чтобы вы все равно могли узнать, если он изменится. Наше руководство по модульному и интеграционному тестированию объясняет, как их разделить.
Совет: «Проходит локально, не проходит в CI» обычно означает разницу в скорости, часовом поясе, ресурсах или версиях. Запуск тестов в одном и том же образе контейнера локально и в CI устраняет многие из них. Наше руководство по Docker Compose может вам помочь.
Как диагностировать шелушащийся тест
- Подтвердите. Запустите тест несколько раз без изменений. С pytest работает простой цикл оболочки, а плагин, такой как
pytest-repeat, дает вам--count=100. - Записывайте доказательства. Сохраняйте журналы, снимки экрана и трассировки каждого сбоя. Здесь особенно полезна программа просмотра следов драматурга.
- Нажмите на условия. Запустите его с ограниченным процессором, параллельно или в случайном порядке. Чем чаще вы будете терпеть неудачу, тем легче будет учиться.
- Проверьте его в одиночку. Если он всегда проходит в одиночку, но терпит неудачу в наборе, подозревайте общее состояние.
- Ищите закономерности. Проверьте, какие машины, время суток и соседние тесты задействованы.
- Проверьте продукт. Честно спросите, может ли это быть настоящей гонкой в вашем приложении.
Являются ли автоматические повторные попытки исправлением?
Повторные попытки являются полезной краткосрочной подстраховкой, но не панацеей. Одна автоматическая повторная попытка в CI может продолжить работу, пока вы проводите исследование, но тест, для прохождения которого требуется повторная попытка, все равно что-то вам говорит. Хуже того, реальная периодическая ошибка продукта, которая дает сбой в 20% случаев, обычно проходит незамеченной при повторной попытке.
Используйте повторы с этими правилами: ограничьте их одним или двумя, записывайте каждый тест, который требует одного, и относитесь к этому списку как к своему списку дел. Тест, отмеченный как «пройденный при повторной попытке», должен считаться проблемой, которую необходимо исследовать, а не успехом.
Командный процесс для нестабильных тестов
- Измерьте. Отслеживайте, какие тесты терпят неудачу, а затем передайте тот же коммит. Многие инструменты CI и репортеры тестов могут это продемонстрировать.
- Карантин. Удалите подтвержденный нестабильный тест из блокирующего выполнения, чтобы он перестал всем мешать, но продолжайте запускать его и отслеживать.
- Назначьте владельца и срок. Карантин без срока становится просто кладбищем.
- Исправьте или удалите. Если тест не стоит исправлять, удалите его и проверьте, существует ли его покрытие в другом месте.
- Предотвратить. Перед объединением запустите новые тесты несколько раз. Тест, который проваливается один раз из пятидесяти запусков, лучше поймать сейчас, чем через шесть месяцев.
Относитесь к нестабильному тесту как к приоритетной ошибке, а не как к фоновому шуму. Здесь также применяются серьезность и приоритет, как описано в разделе серьезность и приоритет. Если нестабильность широко распространена, прочитайте о более широких ошибках автоматизации тестирования и подумайте, не слишком ли много тестов у вас на уровне браузера.
Итоги
Ненадежные тесты кажутся случайными, но это редко так. Почти каждый из них сводится к предположению о времени, порядке, данных или окружающей среде, которое не всегда верно. Выберите тест, который чаще всего дает сбой в вашем конвейере на этой неделе. Запустите его пятьдесят раз, найдите предположение и исправьте его навсегда. Одно настоящее исправление учит команду большему, чем сотня повторов.
Часто задаваемые вопросы
Что такое слоеный тест?
Ненадежный тест проходит одни прогоны и не проходит другие без каких-либо изменений в коде. Причина может быть в тесте, окружающей среде или продукте.
Что вызывает ненадежные тесты?
Наиболее распространенными причинами являются предположения о времени, общее состояние между тестами, нестабильные селекторы, внешние службы, обработка данных и различия между CI и локальными компьютерами.
Должен ли я удалить нестабильные тесты?
Сначала попытайтесь их исправить. Если тест имеет низкую ценность и требует больших затрат на его исправление, его можно удалить, но убедитесь, что его важное покрытие существует где-то еще.
Должен ли я автоматически повторять неудачные тесты?
Да, в качестве ограниченной системы безопасности, но записывайте каждую повторную попытку и исследуйте ее. Сами по себе повторные попытки скрывают проблемы и могут привести к тому, что настоящие периодические ошибки попадут в рабочую среду.
Как найти ненадежные тесты?
Ищите тесты, которые проваливаются и проходят один и тот же коммит, используя историю CI или инструмент отчетности. Вы также можете запускать подозрительные тесты много раз в цикле.
Продолжайте учиться
Нужна помощь с тестированием, WordPress или SEO?
Мы помогаем командам выпускать лучшее программное обеспечение и развивать свои сайты. Расскажите нам, над чем вы работаете, и мы предложим четкий следующий шаг.
Больше руководств по тестирование, искусственный интеллект и современные разработки
Понравилась эта статья? Выберите тему и сразу приступайте к нашим новейшим руководствам.
Объяснение галлюцинаций искусственного интеллекта: почему языковые модели все выдумывают и как их уменьшить
Галлюцинации ИИ объяснили: что это такое, почему языковые модели их создают, распространенные типы, как уменьшить их с помощью заземления и подсказо…
Читать статью →DevOpsОбъяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация
Переменные среды объясняются на примерах Python, которые мы запускали: их чтение и установка, почему каждое значение является строкой, файлы .env, н…
Читать статью →Разработка ПООбъяснение ограничения скорости API: алгоритмы, заголовки и обработка ошибок 429
Ограничение скорости API объясняется с помощью проведенного нами моделирования: сегмент токенов, фиксированное окно, скользящее окно, 429 ответов, з…
Читать статью →Автоматизация тестированияИздевательство в Python: unittest.mock, патч и ошибки, из-за которых тесты лгут
Издевательство на Python с помощью unittest.mock, протестировано: Mock и return_value, Side_effect, исправление использования имени, спецификация и …
Читать статью →Тестирование ПООбъяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%
Покрытие кода объяснено с помощью реального запуска покрытия.py: покрытие строк, ветвей и условий, чтение отчета, почему 100%-ное покрытие все еще м…
Читать статью →Базы данных и SQLDELETE, TRUNCATE и DROP в SQL: различия, откат и когда использовать каждый из них
Объяснение DELETE, TRUNCATE и DROP на примере протестированного SQL: что каждый из них удаляет, откат, триггеры, сброс идентификаторов, скорость и ч…
Читать статью →Базы данных и SQLОбъяснение представлений SQL: создавайте, используйте и избегайте ловушек, с примерами
Представления SQL, объясненные с помощью запросов, которые мы выполняли: создание представления, результаты в реальном времени, скрытие столбцов, пр…
Читать статью →Разработка ПОИнтерфейс TypeScript и тип: реальные различия, протестированные с помощью компилятора
Интерфейс TypeScript против типа, проверенный реальным компилятором: что могут делать оба, что может делать только один, слияние объявлений, расшире…
Читать статью →Разработка ПООбъяснение Python *args и **kwargs: гибкие функции с понятными примерами
Python *args и **kwargs, объясненные с помощью кода, который мы запускали: упаковка и распаковка, порядок параметров, аргументы только по ключевым с…
Читать статью →Разработка ПОЛямбда-функции Python: синтаксис, реальное использование и когда их не следует использовать
Лямбда-функции Python, объясненные с помощью кода, который мы запускали: синтаксис, сортировка с помощью ключа, карты и фильтра, замыкания, ловушка …
Читать статью →Искусственный интеллектЧто такое ИИ-агенты? Как они работают: можно запустить простой цикл агента
Что такое агенты ИИ? Простое руководство по циклу агента, инструментам, памяти и ограничениям, с небольшим циклом Python, который мы запустили, реал…
Читать статью →DevOpsМониторинг и наблюдаемость: реальная разница с журналами, метриками и трассировками
Объяснение мониторинга и наблюдаемости: известные и неизвестные сбои, журналы, метрики и трассировки, пошаговое руководство по инцидентам при оформл…
Читать статью →DevOpsDocker Volumes против Bind Mounts: правильное сохранение данных контейнера
Объяснение томов Docker и монтирования привязки: почему исчезают данные контейнера, именованные тома, монтирование привязки, tmpfs, примеры создания…
Читать статью →Автоматизация тестированияTDD против BDD: ключевые различия, одна функция, созданная для обоих способов, и когда использовать каждый
TDD и BDD объясняются с помощью одной функции скидки, созданной в обоих направлениях: красно-зеленый рефакторинг с кодом, который мы запускали, сцен…
Читать статью →Тестирование ПОПользовательское приемочное тестирование (UAT): процесс, контрольный список и реальные примеры
Приемочное тестирование пользователей объяснило: кто его проводит, чем оно отличается от контроля качества, семиэтапный процесс, шаблон тестового сц…
Читать статью →Базы данных и SQLОбъяснение значений NULL в SQL: IS NULL, COALESCE, NULLIF и ловушки, которых следует избегать
Значения SQL NULL объяснены с помощью запросов, которые мы выполнили: трехзначная логика, IS NULL, COALESCE, NULLIF, как агрегаты, соединения и сорт…
Читать статью →Базы данных и SQLОператор SQL CASE WHEN: синтаксис, реальные примеры и распространенные ошибки
SQL CASE WHEN объяснен с помощью запросов, которые мы выполняли: простые формы и формы для поиска, порядок условий, повороты с помощью SUM, трюки CO…
Читать статью →Разработка ПОЧто такое JWT? Веб-токены JSON, объясненные на рабочем примере
Что такое JWT? Объяснение JSON Web Tokens: три части: токен, созданный и измененный в коде, который мы запускали, JWT и сеансы, хранилище, токены об…
Читать статью →Разработка ПООбъяснение цикла событий JavaScript: стек вызовов, микрозадачи и таймеры с примерами
Цикл событий JavaScript объясняется с помощью кода, который мы запускали: стек вызовов, очереди задач и микрозадач, почему setTimeout 0 не является …
Читать статью →Разработка ПОГенераторы Python и объяснение доходности: экономьте память с помощью реальных примеров
Генераторы Python и доходность, объясненные с помощью кода, который мы запускали: как они приостанавливаются и возобновляются, измеряют экономию пам…
Читать статью →Искусственный интеллектЧто такое вложения? Простое объяснение векторов и косинусного сходства
Что такое вложения? Простое руководство по векторам, косинусному подобию и семантическому поиску с небольшим примером, который мы использовали, общи…
Читать статью →DevOpsСине-зеленое и канареечное развертывание: различия, компромиссы и как выбрать
Объяснение сине-зеленого и канареечного развертывания: как каждый из них работает, стоимость, риски и откат, стабильное группирование пользователей,…
Читать статью →Автоматизация тестированияНенадежные тесты: что их вызывает и как их исправить навсегда
Ненадежные тесты объясняются на демонстрациях, которые мы запускали: основные причины, способы подтверждения и диагностики, правильные исправления в…
Читать статью →Тестирование ПОНагрузочное тестирование и стресс-тестирование: различия, метрики и примеры k6
Объяснение нагрузочного и стресс-тестирования: что доказывает каждое из них, тесты на всплески и выдержки, важные метрики, почему средние значения в…
Читать статью →Тестирование ПОТестовый пример и тестовый сценарий: ключевые различия, примеры и когда использовать каждый
Объяснение тестового случая и тестового сценария: четкие определения, параллельная таблица, одна функция сброса пароля, взятая из сценариев в случаи…
Читать статью →Базы данных и SQLОптимизация SQL-запросов: чтение планов выполнения и исправление медленных запросов с указанием времени
Оптимизация SQL-запросов проверена на миллионе строк: читайте планы выполнения, добавляйте правильные индексы, исправляйте неразмещаемые фильтры, ис…
Читать статью →Базы данных и SQLSQL UNION против UNION ALL: различия, примеры и проверка производительности
SQL UNION и UNION ALL объяснены с помощью запросов, которые мы выполнили: дубликаты, правила, которым следуют оба, реальная ошибка данных, измеренна…
Читать статью →Разработка ПОУчебное пособие по API JavaScript Fetch: GET, POST, обработка ошибок и тайм-ауты
Учебное пособие по API JavaScript Fetch, протестированное на локальном сервере: GET и POST, почему не выдается ошибка 404, многоразовый помощник, ст…
Читать статью →Разработка ПООбработка файлов Python: чтение, запись, CSV, JSON и pathlib с примерами
Обработка файлов Python объясняется с помощью кода, который мы запускали: открытые режимы, чтение больших файлов, CSV, JSON, pathlib, двоичные файлы…
Читать статью →Разработка ПОКлассы и объекты Python: учебник ООП с реальными примерами
Классы и объекты Python, объясненные с помощью кода, который мы запускали: __init__, методы, атрибуты класса и экземпляра, наследование, свойства, м…
Читать статью →Искусственный интеллектЧто такое большая языковая модель? Как работают LLM, объяснено без шумихи
Что такое большая языковая модель? LLM объясняется простым языком: токены, встраивания, внимание, обучение, температурные и контекстные окна, крошеч…
Читать статью →DevOpsКоманды Linux для начинающих: основное руководство по терминалу с примерами
Команды Linux для начинающих, сгруппированные по задачам: навигация, файлы, просмотр журналов, grep, каналы, разрешения, процессы и архивы, с практи…
Читать статью →Автоматизация тестированияSelenium vs Playwright: ключевые различия, сравнение кода и что выбрать
Сравнение Selenium и Playwright: архитектура, ожидание, поддержка браузера и языка, отладка, одинаковый тест в обоих инструментах, советы по миграци…
Читать статью →Тестирование ПОРазделение эквивалентности: метод проектирования тестов, объясненный на рабочих примерах
Объяснение эквивалентного разделения: как найти действительные и недействительные разделы, рабочие примеры для возрастов, паролей и купонов, проведе…
Читать статью →Тестирование ПОСерьезность и приоритет в тестировании программного обеспечения: различия, матрица и реальные примеры
Объяснение серьезности и приоритета при тестировании программного обеспечения: четкие определения, четырехсторонняя матрица с примерами, шкалы уровн…
Читать статью →Базы данных и SQLУчебное пособие по подзапросам SQL: типы, коррелирующие подзапросы и EXISTS с примерами
Руководство по подзапросам SQL с выполненными нами запросами: скалярные подзапросы, IN, FROM и SELECT, коррелированные подзапросы, EXISTS, ловушка N…
Читать статью →Базы данных и SQLОбъяснение транзакций SQL и ACID: COMMIT, ROLLBACK и уровни изоляции
Транзакции SQL и ACID, объясненные с помощью банковского перевода, который мы выполнили: COMMIT, ROLLBACK, точки сохранения, блокировки, уровни изол…
Читать статью →Разработка ПОGit merge vs rebase: отличия, примеры и когда что использовать
Git merge vs rebase с реальным выводом log, сравнительной таблицей, золотым правилом rebase и безопасным рабочим процессом для команды.
Читать статью →Разработка ПОГенераторы списков в Python: синтаксис, примеры и когда их избегать
Генераторы списков в Python: синтаксис, фильтры, if-else, вложенные циклы, измеренное время и случаи, когда обычный цикл for лучше.
Читать статью →Базы данных и SQLSQL JOIN: объяснение с примерами (Inner, Left, Right, Full)
Разберите SQL JOIN шаг за шагом на примерах таблиц и запросов: INNER, LEFT, RIGHT, FULL, CROSS и SELF, а также частые ошибки и советы по производите…
Читать статью →