惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
月光博客
月光博客
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
博客园_首页
雷峰网
雷峰网
量子位
有赞技术团队
有赞技术团队
博客园 - 叶小钗
博客园 - 聂微东
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
小众软件
小众软件
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
Apple Machine Learning Research
Apple Machine Learning Research
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Почему E2E-тесты флакают всё чаще и как с этим жить
mikhail-lankin · 2026-06-15 · via Все публикации подряд на Хабре

Простой

9 мин

5

Почему, несмотря на накопленный опыт и современный инструментарий, число флаки‑тестов растёт год от года? Исследование BitRise 2025 года показало, что доля команд, которым приходится сталкиваться с флаки-тестами, выросла с 10% в 2022-м до 26% в 2025-м.

Нестабильные тесты сильно бьют по рабочим процессам: из опроса 1600 человек в 2023 году стало ясно, что флаки-тесты съедают 8% рабочего времени, почти столько же, сколько занимает наладка и поддержка тестовых сред. Но реальный вред флаков гораздо больше: они подрывают доверие к здоровым тестам и ставят под вопрос всю автоматизацию.

Вряд ли количество флаков растёт из-за того, что люди разучились писать атомарные тесты и правильно проектировать архитектуру приложений. Скорее проблема в том, что усложняется среда разработки и тестирования:

  • Больше этапов в пайплайнах

  • Более сложные рабочие процессы

  • Больше сторонних зависимостей

Сложность среды, сторонние зависимости - всё это бьёт в первую очередь по E2E-тестам. Проблема в том, что компенсировать эти источники сложности может быть очень дорого. И именно об этом я хочу поговорить сегодня: насколько дорого обеспечить стабильность E2E-тестов?

1. Частые причины нестабильности

Есть много работ с классификацией источников нестабильности во флаки-тестах; опираясь на них, я пройду по наиболее частым причинам.

Ожидания

Причина львиной доли флаков - проблемы с ожиданиями (подробнее про это тут и тут). Автору теста нужно угадать верхнюю границу, дольше которой тест не будет ждать отклика от браузера. Если дали слишком мало времени - тест упадёт. Если дали слишком много - тест будет медленный. Эта проблема - одна из главных причин, по которым переходят с Selenium на, скажем, Playwright с автоматическими ожиданиями. Сам по себе этот переход может сильно урезать количество флаков.

Использование общих ресурсов

Другая важная причина - использование общих ресурсов. Вот простой пример (по мотивам статьи):

def append_data(data, path, encoding="utf-8"):
    """
    Добавляем данные в конец файла
    """

    # готовим файл
    with open(path, 'a', encoding=encoding) as data_file:    
        # записываем данные
        data_file.write(data)
	
	
def test_data_written():
    data = "Очень важное сообщение"
    file = Path("тестовый_файл.txt")  

    # выполняем тестируемую функцию
    append_data(data, "тестовый_файл.txt")

    # проверяем запись
    assert file.read_text(encoding="utf-8") == data

(все примеры доступны здесь)

Тест test_data_written использует ресурс - файл тестовый_файл.txt. Предположим, к этому файлу есть доступ у других тестов. Если на момент выполнения теста в файле уже есть содержимое, тест будет красным: мы прочитаем не только то, что записали, но и то, что было раньше. Если же файл чистый, тест пройдёт, хотя и функция, и тест никак не изменились: это флак.

Зависимость от порядка выполнения

Эта причина, на самом деле, связана с предшествующей. Если у тестов общие ресурсы и один из них не "убрал за собой", все последующие упадут.

Добавим к нашему примеру ещё один тест, скажем, на запись с другой кодировкой:

def test_utf16_data_written():
    data = "Друге важное сообщение"
    file = Path("тестовый_файл.txt")
    encoding = "utf-16" 

    # выполняем тестируемую функцию
    append_data(data, "тестовый_файл.txt", encoding)

    # проверяем запись
    assert file.read_text(encoding) == data

Если его выполнить в одиночку, он пройдёт успешно, а если выполнить после test_data_written - упадёт, когда попытается прочитать записанный прошлым тестом текст в другой кодировке. Порядок выполнения влияет на результат: это флак.

Параллельное выполнение

Плохо настроенное параллельное выполнение тестов - тоже одна из наиболее частых причин сбоев. Если в нашем примере мы запустим тесты параллельно (например, с помощью pytest-xdist), они тоже упадут, из-за того, что запишут одновременно в один файл тексты с разными кодировками.

Зависимость от внешних систем

Предположим, мы написали функцию, запрашивающую у стороннего сервиса наш IP, и протестировали её:

import ipaddress
import requests

def get_my_ip():
    url = "https://api64.ipify.org?format=json"
    
    # отправляем запрос к сервису
    response = requests.get(url).json()
    
    # возвращаем только ip
    return response["ip"]
    
def test_get_my_ip_returns_something():
	# вызываем проверяемую функцию
	ip = get_my_ip()

	# вместо ассёрта вызываем функцию, которая упадёт при неправильном ip
    ipaddress.ip_address(ip)

Запустили тест, он прошёл зелёным. А на следующий день акула погрызла кабель на дне океана.

Акула грызет кабель

Акула грызет кабель

Мы не контролируем внешнюю систему, и любые изменения в ней могут повлиять на результат тестов - без каких-либо изменений в самих тестах или в тестируемом коде. Опять флак.

2. Избавляемся от общего состояния

В нашем примере в большинстве указанных случаев причиной было общее состояние у тестов. Как от него избавиться? Этому нас давно научили классики: нужно писать изолированные тесты.

Вернёмся в наш пример. Вместо того чтобы вслепую хватать общий файл и записывать в конец наши данные, сделаем так, чтобы каждый тест работал с отдельным, уникальным файлом, который будет создаваться только для этого теста и удаляться после запуска:

import uuid
from pathlib import Path
import pytest
  
from demo_isolation_cost.test_function import append_data

@pytest.fixture
@profile
def temp_file():
	"""
	Временный файл, удаляемый после работы теста.
	"""
    # создаём уникальный путь
    # (это можно было сделать родной фикстурой Pytest
    # tmp_path, но мы будем измерять время выполнения
    # операций с файлами, поэтому важно, чтобы они были
    # в нашем коде, а не на стороне Pytest)
    path = Path(f"test_{uuid.uuid4().hex[:8]}.txt")

    # создаём файл
    path.write_text("")

    # передаём управление тесту
    yield path

    # убираемся за тестом
    path.unlink(missing_ok=True)

  
@profile
def test_data_written(temp_file):
    data = "Очень важное сообщение"  

    # выполняем тестируемую функцию
    append_data(data, temp_file) 

    # проверяем, что данные записаны 
    assert temp_file.read_text(encoding="utf-8") == data


@profile
def test_utf16_data_written(temp_file):
    data = "Друге важное сообщение"
    encoding = "utf-16" 

    # выполняем тестируемую функцию
    append_data(data, temp_file, encoding)  

    # проверяем запись
    assert temp_file.read_text(encoding) == data

Теперь неважно, сколько раз и в каком порядке запускаются тесты, они не будут мешать друг другу.

Но изменилось и кое-что ещё. Теперь вместо двух обращений к файлу (запись, чтение) каждый тест выполняет четыре обращения (создание, запись, чтение, удаление). Попробуем измерить время, которое занимают эти операции:

Результаты профилирования

Результаты профилирования

Оказывается, наше создание и удаление файла заняло больше времени, чем сам тест.

Конечно, здесь речь идёт о долях миллисекунды, и это не повод экономить. Но это из-за того, что наш пример, во-первых, детский, а, во-вторых, относится к уровню юнит-тестов. Если же мы поднимемся выше по пирамиде тестирования, цена доступа к ресурсам быстро перестаёт быть детской.

3. Цена изоляции

Мартин Фаулер, описывая важность атомарных тестов, писал:

"...я считаю крайне важным сохранять тесты изолированными. Если тесты изолированы должным образом, их можно запускать в любом порядке. Но по мере продвижения к функциональным тестам с более широким операционным охватом поддерживать такую изоляцию становится всё сложнее."

Все уже давно научились настраивать быстрые сюиты изолированных юнит-тестов, которые можно было бы запускать одним щелчком и использовать для проверки каждого пулл-риквеста. Сделать это на уровне E2E гораздо сложнее. Здесь оказывается, что изоляция тестов дорого стоит.

Ресурсы

Google использует при запуске тестов практику «герметичных сред»:

"Чтобы решить эти проблемы, в Google мы сделали ставку на эфемерные герметичные SUT (тестируемые системы) и интегрировали их в нашу CI/CD-инфраструктуру. Сначала мы создали универсальный фреймворк для определения, настройки и запуска SUT.

...

При этом подходе все зависимости теста — это компоненты SUT, одобренные командой, которая владеет соответствующей зависимостью. Так снижается нестабильность, характерная для традиционных общих тестовых зависимостей. Наша инфраструктура запускает эти компоненты в изолированных контейнерах, и, если у вас достаточно аппаратных ресурсов, все они могут быть запущены на одной машине. Это устраняет нестабильность и задержки, возникающие при вызовах через физическую сеть.

...

Так как SUT можно запускать отдельно для каждого теста, мы устраняем проблемы, возникающие из-за того, что несколько тестов выполняются параллельно и записывают одни и те же данные, или из-за того, что предыдущий тест оставил хранилище данных в несогласованном состоянии. Вы всегда можете быть уверены, что каждый тест начинается с предсказуемого, чистого состояния."

Звучит как настоящее волшебство: не нужно ничего вручную убирать, всё окружение теста обнуляется автоматически. Во что обходится это волшебство? Запуск некоторых эвфемерных систем может занимать 10 минут или полчаса, и инженеры Google считают это решение хоть и крайне полезным, но дорогим.

Повторим для себя: Google - гигант в духе киберпанка, рыночная капитализация которого сравнима с ВВП богатой страны - считает это решение дорогим, использует выборочно, и признаёт, что не победил флаки.

Да, этот гигант работает с системами огромной сложности. Но инициализировать базу данных для каждого теста дорого и для простых смертных. Cypress сделал изоляцию тестов поведением по умолчанию, и считает это лучшей практикой - что, безусловно, очень здорово. Но при этом опция testIsolation: false всё равно остаётся, и подразумевается, что она будет использоваться именно для тяжеловесных E2E тестов.

Конечно, это преодолимая проблема, и на то, чтобы сделать изолированный запуск дешёвым, тратятся огромные усилия. И это тоже своя цена: создание хорошей инфраструктуры занимает много времени.

Время на создание инфраструктуры

Перейдём от зарубежного киберпанка к отечественному: Сбер, борясь с нестабильностью тестов, несколько лет назад создал механизм, который генерирует всю иерархию бизнес-сущностей для каждого теста, и удаляет её после завершения теста. Реализация этих генераторов заняла больше года, потому что для обеспечения максимального быстродействия тестов решили генерировать сущности не через API приложения, а через прямые вызовы к базе данных.

Справедливости ради нужно сказать, что:

  1. приложение было на тот момент тяжеловесным и монолитным;

  2. создание генераторов дало много побочных преимуществ - в частности, взглянув на генераторы, которые использует тест, теперь сразу понятно, с какими данными этот тест работает.

Достоверность

Ещё одна цена изоляции - достоверность: фокусируя тесты на отдельных операциях, мы уходим от реальных условий использования. В т.ч. из-за этого классический подход "пирамиды тестов" сейчас подвергают критике. С этим отчасти связано то, что более независимый тест может быть сложнее понять, поскольку он оторван от контекста.

Конечно, это не значит что стоит вернуться к практике двадцатилетней давности, когда писались в основном E2E: надёжность и быстрота с лихвой перевешивают достоверность.

Выводы

Всё это не недостатки, а именно цена. Никто не перестанет из-за этого создавать механизмы изолированного запуска тестов. Но вопрос в том, что создание этих механизмов обходится дорого по ресурсам и по времени, а пока это время идёт, с флаками всё равно нужно что-то делать.

4. Отслеживать дешевле, чем искоренять

К сожалению, чаще всего оказывается так, что правильная инфраструктура для "отлова" флаки стоит гораздо дешевле, чем правильная инфраструктура для их искоренения. Это не выбор или-или - нужно и то, и другое; но пока создаётся вторая, первую стоит уже иметь.

Какие здесь есть инструменты? Вот примерный перечень (не взаимоисключающий):

Перезапуск

Чтобы понять, случайно падение или нет, тест можно перезапустить. Иногда это очень дёшево - для того же Pytest существует [специальный плагин], а в сам Pytest версию по настоянию сообщества добавили опцию --lf, позволяющую запускать только упавшие на прошлом прогоне тесты. Правда, эта опция обеспечивает только локальный перезапуск у себя на машине; реализовать перезапуски масштабно тоже может быть дорого - тому же Сберу для этого пришлось переписать все сообщения об ошибках. Но они всё равно сделали это раньше, чем взялись за генераторы сущностей.

Слежение

TMS обеспечивают сбор аналитики по тестам, и с помощью неё можно определить, какие именно тесты падают случайно. По нынешним временам на этом этапе могут помочь нейросети: существуют (обычно платные) инструменты, обнаруживающие флаки по результатам тестов, а некоторые даже помогают исправлять причины нестабильностей.

Сортировка

Главное, что позволяет сократить вред от флаки - автоматическая сортировка результатов. Самый очевидный здесь вариант - настраиваемые категории в Allure Report, они автоматически сортируют упавшие тесты в т.ч. на основе сообщений об ошибках; если причина нестабильности известна, на неё можно не тратить время - система сама связывает с ней результаты тестов.

Карантин

Наконец, если нестабильный тест нельзя исправить сразу, он уходит в карантин, где ждёт своего часа - в идеале с выделением времени на работу с ним, как с любым техническим долгом.

5. Заключение

Почему, несмотря на накопленный опыт и современный инструментарий, число флаки‑тестов растёт год от года? Причина в том, что создать инфраструктуру, которая бы сделала флаки невозможными, очень, очень дорого - и чем сложнее продукты, тем дороже. Конечно, мы не перестанем из-за этого совершенствовать архитектуру и способы чистого запуска тестов. Но пока мы этим занимаемся, должна уже существовать инфраструктура для слежения, отлова и сортировки флаков.