Контекст для тестов

Допустим, мы хотим протестировать этот жутко полезный сервер:

// Дает ответы на все вопросы.
type Server struct{}

// Возвращает ответ сервера.
func (s *Server) Get(query string) int {
    return 42
}

// Запускает сервер. Остановка через отмену контекста.
func startServer(ctx context.Context) *Server {
    go func() {
        select {
        case <-ctx.Done():
            // Освобождаем ресурсы.
        }
    }()
    return &Server{}
}

Вот чудесный тест, который я написал:

func Test(t *testing.T) {
    srv := startServer(context.Background())
    if srv.Get("how much?") != 42 {
        t.Fatal("unexpected value")
    }
}

Тест проходит, но есть проблемка: я использовал пустой контекст, так что на самом деле сервер не остановился. Такие утечки ресурсов могут стать проблемой — особенно если тестов много.

Исправить это несложно — достаточно использовать контекст с отменой:

func Test(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    srv := startServer(ctx)
    if srv.Get("how much?") != 42 {
        t.Fatal("unexpected value")
    }
}

Но практика показывает, что люди часто забывают это делать 🤷‍♀️

Поэтому в Go 1.24 добавили метод T.Context. Он возвращает контекст, который автоматически отменяется перед тем, как тест завершится:

func Test(t *testing.T) {
    srv := startServer(t.Context())
    if srv.Get("how much?") != 42 {
        t.Fatal("unexpected value")
    }
}

Удобно!

★ Подписывайтесь на новые заметки.