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

推荐订阅源

WordPress大学
WordPress大学
D
Docker
Microsoft Azure Blog
Microsoft Azure Blog
S
SegmentFault 最新的问题
Recent Announcements
Recent Announcements
博客园 - 司徒正美
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
宝玉的分享
宝玉的分享
Spread Privacy
Spread Privacy
T
The Exploit Database - CXSecurity.com
腾讯CDC
T
Tenable Blog
aimingoo的专栏
aimingoo的专栏
T
The Blog of Author Tim Ferriss
Microsoft Security Blog
Microsoft Security Blog
Latest news
Latest news
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
O
OpenAI News
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
P
Palo Alto Networks Blog
博客园 - 【当耐特】
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
Cisco Talos Blog
Cisco Talos Blog
P
Privacy International News Feed
J
Java Code Geeks
IT之家
IT之家
P
Privacy & Cybersecurity Law Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Proofpoint News Feed
NISL@THU
NISL@THU
量子位
Recent Commits to openclaw:main
Recent Commits to openclaw:main
C
CERT Recently Published Vulnerability Notes
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Tor Project blog
V
V2EX
博客园_首页
The Last Watchdog
The Last Watchdog
雷峰网
雷峰网
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
A
About on SuperTechFans
S
Schneier on Security
Hacker News: Ask HN
Hacker News: Ask HN
Hugging Face - Blog
Hugging Face - Blog
Simon Willison's Weblog
Simon Willison's Weblog
C
CXSECURITY Database RSS Feed - CXSecurity.com
人人都是产品经理
人人都是产品经理

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Почему 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. Заключение

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