TGViewer
Библиотека Go-разработчика | Golang Библиотека Go-разработчика | Golang @goproglib · 24.1K subscribers
Post #7188 2.58K
😉 Тесты для конкурентного кода

Каждый, кто писал тесты для конкурентного Go, знает эту боль. Нужно проверить, что что-то произошло (или не произошло) после работы горутины. А единственный способ это сделать — воткнуть time.Sleep и надеяться, что на CI этого хватит.

Выглядело это примерно так:
func TestTimeout(t *testing.T) {
// ... настраиваем логику таймаута ...

time.Sleep(10 * time.Millisecond) // авось хватит

if !timedOut {
t.Fatal("expected timeout, got none")
}
}


10 миллисекунд на один тест — мелочь. Но когда таких тестов сотни, набегают секунды пустого ожидания. А на нагруженном CI раннере 10ms иногда недостаточно, и тест становится flaky.

Что делает synctest

Пакет testing/synctest появился экспериментально в Go 1.24 за флагом GOEXPERIMENT=synctest. В Go 1.25 он вышел в GA с обновлённым API — флаг больше не нужен. Вместо старого synctest.Run используем synctest.Test. Старый API удалён в Go 1.26.

Пакет решает проблему двумя механизмами.

Bubble. synctest.Test() оборачивает тест в изолированную среду. Все горутины, запущенные внутри, принадлежат этому «пузырю».

Виртуальное время. Внутри пузыря time.Sleep, таймеры и тикеры работают на фейковых часах. Реальное время не идёт. Часы сдвигаются только тогда, когда все горутины в пузыре заблокированы и ждут. Тест с пятисекундным таймаутом выполняется за микросекунды.

`synctest.Wait()` блокирует выполнение, пока все остальные горутины в пузыре не окажутся в состоянии устойчивой блокировки. После возврата из Wait() мы точно знаем, что система обработала всё, что могла.

Вот тот же тест, переписанный на synctest:
import "testing/synctest"

func TestTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
const timeout = 5 * time.Second
ctx, cancel := context.WithTimeout(
context.Background(), timeout,
)
defer cancel()

time.Sleep(timeout - time.Nanosecond)
synctest.Wait()
if err := ctx.Err(); err != nil {
t.Fatalf("context expired early: %v", err)
}

time.Sleep(time.Nanosecond)
synctest.Wait()
if ctx.Err() != context.DeadlineExceeded {
t.Fatalf("expected DeadlineExceeded, got %v", ctx.Err())
}
})
}


Никакого реального ожидания. Никаких flaky. Никаких изменений в тестируемом коде.

Что считается «устойчивой блокировкой»

Не все блокирующие операции равны. synctest.Wait() учитывает только те, которые могут быть разблокированы исключительно горутинами внутри пузыря.

Учитываются: отправка и получение из каналов, созданных внутри пузыря, time.Sleep, sync.Cond.Wait, sync.WaitGroup.Wait.

Не учитываются: sync.Mutex (обычно захватывается ненадолго), сетевые вызовы и чтение файлов (их может разблокировать ядро ОС).

Сетевые тесты

Для тестирования сетевого взаимодействия внутри пузыря используем net.Pipe() — это in-memory соединения, которые synctest считает устойчиво блокирующими:
func TestServerHandshake(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
serverConn, clientConn := net.Pipe()
defer serverConn.Close()
defer clientConn.Close()

go runServer(serverConn)
go runClient(clientConn)
synctest.Wait()
// Все горутины заблокированы — хендшейк завершён
})
}


Запуск

На Go 1.25+ никаких дополнительных флагов:
go test ./...


Если вы до сих пор боретесь с flaky-тестами в конкурентном коде, synctest убирает главную причину — гадание на time.Sleep. Тесты становятся быстрыми и детерминированными.

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoToProduction
  • 👍 14
  • ❤ 2
More from @goproglib
  1. Sep 30, 2026🧑‍💻 Эмулятор AWS-сервисов Kumo — это небольшой инструмент на Go для локальной имитации A…
  2. Sep 29, 2026👨‍💻 Библиотека для написания LSP-серверов Написать свой Language Server с нуля на Go сло…
  3. Sep 28, 2026🤔 Вопрос с собеседования по Go Что выведет программа? ❤️ — 1 true / 0 false 🔥 — 1 true /…
  4. Sep 28, 2026👩‍💻 Что на самом деле происходит внутри Go map? После Go 1.24 обычный map внутри работае…
  5. Sep 26, 2026🔥 В Go 1.27 появился portable SIMD До этого SIMD-оптимизации в Go требовали архитектурног…
  6. Sep 25, 2026🤡🤡 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека Go-разработчика #GoGiggle
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →