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

推荐订阅源

N
News | PayPal Newsroom
H
Hackread – Cybersecurity News, Data Breaches, AI and More
雷峰网
雷峰网
NISL@THU
NISL@THU
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
博客园_首页
P
Privacy & Cybersecurity Law Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
Know Your Adversary
Know Your Adversary
小众软件
小众软件
人人都是产品经理
人人都是产品经理
WordPress大学
WordPress大学
博客园 - 聂微东
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tor Project blog
C
Cybersecurity and Infrastructure Security Agency CISA
V
V2EX
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
J
Java Code Geeks
M
MIT News - Artificial intelligence
PCI Perspectives
PCI Perspectives
T
The Blog of Author Tim Ferriss
美团技术团队
Jina AI
Jina AI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 最新话题
Cloudbric
Cloudbric
Webroot Blog
Webroot Blog
V2EX - 技术
V2EX - 技术
爱范儿
爱范儿
S
Securelist
MyScale Blog
MyScale Blog
B
Blog
AI
AI
L
LINUX DO - 热门话题
Security Archives - TechRepublic
Security Archives - TechRepublic
The Register - Security
The Register - Security
I
Intezer
有赞技术团队
有赞技术团队
Google Online Security Blog
Google Online Security Blog
云风的 BLOG
云风的 BLOG
H
Help Net Security
D
DataBreaches.Net
K
Kaspersky official blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
The Last Watchdog
The Last Watchdog
Attack and Defense Labs
Attack and Defense Labs
酷 壳 – CoolShell
酷 壳 – CoolShell

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 миллионов точек без потерь
Retry в Go автотестах: как перестать бояться flaky-тестов
Никита Филонов · 2026-06-24 · via Все публикации подряд на Хабре

Средний

5 мин

253

Вступление

Flaky-тесты — неизбежная реальность, как бы нам этого ни хотелось. Особенно в интеграционных и E2E-сценариях, где автотесты зависят от сети, окружения и нестабильных тестовых стендов. Любой временный сбой — и тест падает, а его перезапуск превращается в проблему.

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

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

В этой статье разберёмся, какие подходы к retry в Go приводят к проблемам и как выглядит корректная модель повторного запуска автотестов. В качестве примера будем использовать Axiom.

Как обычно реализуют retry в Go

Когда тесты начинают флапать, первое, что приходит в голову — просто перезапустить тест несколько раз. В Go это обычно выглядит примерно так.

Наивный retry

func TestSomething(t *testing.T) {
	for i := 0; i < 3; i++ {
		err := runTestScenario()
		if err == nil {
			return
		}
	}

	t.Fatal("test failed after retries")
}

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

В результате:

  • состояние между попытками не сбрасывается;

  • ресурсы переиспользуются повторно;

  • побочные эффекты накапливаются;

  • поведение теста становится недетерминированным.

Фактически это не retry теста, а несколько вызовов одной и той же логики в одном и том же контексте.

Или ещё хуже

Часто следующий шаг — попытка «сделать красиво» через t.Run:

func TestSomething(t *testing.T) {
	for i := 0; i < 3; i++ {
		t.Run(fmt.Sprintf("attempt-%d", i), func(t *testing.T) {
			runTestScenario(t)
		})
	}
}

Здесь кажется, что мы запускаем тест заново. Но на практике это всё тот же один тест с общим состоянием:

  • фикстуры и ресурсы не переинициализируются;

  • контекст и глобальное состояние протекают между попытками;

  • невозможно гарантировать чистый lifecycle;

  • CI начинает вести себя непредсказуемо.

Такой retry часто делает flaky-тесты ещё более flaky, чем они были изначально.

В обоих случаях проблема одна и та же: retry реализуется как повторный вызов кода, а не как повтор модели выполнения теста.

Как сделать retry правильно

После антипримеров важно зафиксировать одну ключевую мысль:

retry — это не повторный вызов кода теста.
retry — это повтор всей модели выполнения теста.

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

В Axiom retry встроен в сам механизм выполнения теста. Он не оборачивает тестовую логику в цикл и не пытается «поймать» ошибку. Вместо этого каждая попытка запускается как отдельное выполнение теста с новым runtime-состоянием.

Важно сразу подчеркнуть: никакой магии здесь нет. Retry в Axiom — это всё тот же повторный запуск теста. Разница в том, на каком уровне он выполняется.

Минимальный пример retry в Axiom

package example_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/Nikita-Filonov/axiom"
)

func TestRetryExample(t *testing.T) {

	// -------------------------------------------------------------------------
	// Runner-level retry policy
	// -------------------------------------------------------------------------
	// Глобальная политика retry для всех тестов,
	// если Case не переопределяет её явно.
	runner := axiom.NewRunner(
		axiom.WithRunnerRetry(
			axiom.WithRetryTimes(2),
			axiom.WithRetryDelay(1*time.Second),
		),
	)

	// -------------------------------------------------------------------------
	// Case-level override
	// -------------------------------------------------------------------------
	// Конкретный тест может переопределить retry-политику.
	c := axiom.NewCase(
		axiom.WithCaseName("retry example"),
		axiom.WithCaseRetry(
			axiom.WithRetryTimes(3),
			axiom.WithRetryDelay(500*time.Millisecond),
		),
	)

	attempt := 0

	runner.RunCase(t, c, func(cfg *axiom.Config) {

		attempt++
		fmt.Println("attempt:", attempt)

		// Имитируем flaky-поведение
		if attempt < 3 {
			t.Fail() // триггерит retry
		}

		cfg.Step("finalize", func() {
			fmt.Println("success on attempt", attempt)
		})
	})
}

Здесь retry задаётся декларативно и не вмешивается в код теста. Политика может быть определена глобально и при необходимости переопределена локально — без копипаста и без условной логики внутри тестов.

Что именно происходит при retry в Axiom

Когда тест запускается через Runner, Axiom сначала собирает полную конфигурацию выполнения — объединяя настройки Runner’а и конкретного Case. Эта конфигурация описывает всё, что относится к выполнению теста: метаданные, retry-политику, контекст, хуки, фикстуры, плагины и правила исполнения.

При retry происходит следующее:

  1. Конфигурация теста собирается заново. Для каждой попытки Axiom создаёт новый runtime-снимок (Config), объединяя Runner и Case. Это означает, что retry — это не повтор в рамках уже существующего состояния, а новый запуск с теми же правилами.

  2. Контекст и runtime не переиспользуются. Контекст выполнения, runtime-обёртки и execution-политики инициализируются заново. Ничего не «протекает» между попытками, если это явно не задано конфигурацией.

  3. Фикстуры получают новый жизненный цикл. Все фикстуры считаются ленивыми и привязанными к конкретной попытке. При retry они будут пересозданы при первом обращении и корректно очищены после завершения попытки.

  4. Хуки и плагины применяются повторно. Before/after-хуки, middleware и плагины выполняются так же, как при обычном запуске теста — но для каждой попытки отдельно.

И только после этого выполняется тело теста.

Таким образом, retry в Axiom — это повтор execution model, а не повтор функции.

Под капотом retry может выглядеть как обычный цикл — и это нормально. Ключевая разница не в реализации, а в точке повтора.

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

Заключение

В этой статье мы разобрали, как можно корректно реализовать retry в Go-автотестах и почему наивные подходы быстро приводят к нестабильному поведению тестов.

Axiom позволяет задать retry как часть тестового рантайма: политика повторных попыток конфигурируется централизованно на уровне Runner’а и при необходимости переопределяется локально на уровне конкретного теста. Это даёт контроль и предсказуемость — retry перестаёт быть хаотичным костылём и становится осознанной политикой выполнения.

Такой подход не лечит flaky-тесты, но позволяет работать с ними аккуратно: без протекания состояния, без скрытых побочных эффектов и без необходимости встраивать retry-логику прямо в тестовый код.