Nano Hash - криптовалюты, майнинг, программирование

Состояние гонки в httptestserver при смене обработчика

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

func TestAPICaller_RunApiMethod(t *testing.T) {

    server := httptest.NewServer(http.HandlerFunc(nil))
    defer server.Close()

    for _, test := range testData {     
        server.Config.Handler = http.HandlerFunc(test.handler)

        t.Run(test.Name, func(t *testing.T) {
           ... some code which calls server
        }
    })
}

Этот код дает гонку при запуске с «go test -race». Вероятно, это потому, что сервер работает в горутине, и я одновременно пытаюсь изменить обработчик. Я прав?

Если я попробую альтернативный код, где я создаю новый сервер для каждого теста, то никаких гонок:

func TestAPICaller_RunApiMethod(t *testing.T) {

    for _, test := range testData {     
        server := httptest.NewServer(http.HandlerFunc(test.handler))

        t.Run(test.Name, func(t *testing.T) {
           ... some code which calls server
        }

        server.Close()
    })
}

Итак, первый вопрос, как лучше всего использовать один сервер для части тестов и обработчика изменений на лету без гонок? И стоит ли с точки зрения производительности иметь один сервер вместо создания новых?


Ответы:


1

httptest.Server не был "предназначен" для изменения своего обработчика. Вы можете изменить его обработчик только в том случае, если вы создали его с помощью httptest.NewUnstartedServer(), и только до того, как вы запустите его с помощью Server.Start() или Server.StartTLS().

Просто создайте и запустите новый сервер, если вы хотите протестировать новый обработчик.

Если у вас действительно много обработчиков, которые вы хотите протестировать таким образом, и производительность критична для вас, вы можете создать обработчик «мультиплексор» и передать его одному httptest.Server. Когда вы закончите тестирование обработчика, измените «состояние» обработчика мультиплексора, чтобы переключиться на следующий тестируемый обработчик.

Давайте посмотрим на примере, как это может выглядеть (поместите весь этот код в TestAPICaller_RunApiMethod):

Допустим, мы хотим протестировать следующие обработчики:

handlersToTest := []http.Handler{
    http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte{0}) }),
    http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte{1}) }),
    http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte{2}) }),
}

Вот пример обработчика мультиплексора:

handlerIdx := int32(0)
muxHandler := func(w http.ResponseWriter, r *http.Request) {
    idx := atomic.LoadInt32(&handlerIdx)
    handlersToTest[idx].ServeHTTP(w, r)
}

Который мы используем для тестового сервера:

server := httptest.NewServer(http.HandlerFunc(muxHandler))
defer server.Close()

И код для проверки всех обработчиков:

for i := range handlersToTest {
    atomic.StoreInt32(&handlerIdx, int32(i))
    t.Run(fmt.Sprint("Testing idx", i), func(t *testing.T) {
        res, err := http.Get(server.URL)
        if err != nil {
            log.Fatal(err)
        }
        data, err := ioutil.ReadAll(res.Body)
        if err != nil {
            log.Fatal(err)
        }
        res.Body.Close()

        if len(data) != 1 || data[0] != byte(i) {
            t.Errorf("Expected response %d, got %d", i, data[0])
        }
    })
}

Здесь следует отметить одну вещь: «состояние» обработчика мультиплексора — это переменная handlerIdx. Поскольку обработчик мультиплексора вызывается из другой горутины, доступ к этой переменной должен быть синхронизирован (потому что мы записываем в нее, а горутина сервера ее читает).

17.07.2018
  • Вы запускаете детектор гонок? Я на мобильном телефоне, я не могу сделать это сам, но мне кажется, что это гонка данных. 17.07.2018
  • handlerIdx работает на другой горутине с сервером. Но пока это только для этих тестов и тесты не парелят, я думаю тогда все ок. 17.07.2018
  • @leafbebop Ты прав. Прогнал детектор гонок, ничего не нашел, так как тесты запускались последовательно. Но вы правы, доступ к handlerIdx должен быть синхронизирован. 17.07.2018
  • Спасибо за объяснение и решение. Немного сбивает с толку то, что в документации никогда не упоминается, что небезопасно устанавливать Config.Handler напрямую, еще хуже то, что тесты работают нормально без флага -race. Я буду запускать новый сервер для каждого теста. 17.07.2018
  • Новые материалы

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

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

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

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..