dqf67993 2018-07-17 11:35
浏览 50
已采纳

更改处理程序时,httptest服务器中的竞争条件

I have a slice of tests and I want to run them over one instance of httptest.Server. Each test has its own handler function.

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
        }
    })
}

This code gives a race when running with "go test -race". It is probably because server runs in goroutine and I'm trying to change a handler concurrently. Am I correct?

If I try alternative code where I create a new server for every test, then no races:

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()
    })
}

So first question what is the best way to use one server for a slice of tests and change handler on the fly without races? And is it worth it in terms of performance to have one server instead of creating new ones?

  • 写回答

1条回答 默认 最新

  • doupian9798 2018-07-17 11:48
    关注

    httptest.Server was not "designed" to change its handler. You may only change its handler if you've created it with httptest.NewUnstartedServer(), and only before you start it with Server.Start() or Server.StartTLS().

    Just create and start a new server when you want to test a new handler.

    If you have really a lot of handlers you want to test this way and performance is that critical for you, you may create a "multiplexer" handler, and pass that to a single httptest.Server. When you're done testing a handler, change the "state" of the multiplexer handler to switch over to the next testable handler.

    Let's see an example how it could look like (put all these code into TestAPICaller_RunApiMethod):

    Let's say we want to test the following handlers:

    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}) }),
    }
    

    Here's an example multiplexer handler:

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

    Which we use for the test server:

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

    And code to test all handlers:

    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])
            }
        })
    }
    

    One thing to note here: the "state" of the multiplexer handler is the handlerIdx variable. Since the multiplexer handler is called from another goroutine, access to this variable must be synchronized (because we're writing to it and the server's goroutine reads it).

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 救!ENVI5.6深度学习初始化模型报错怎么办?
  • ¥30 eclipse开启服务后,网页无法打开
  • ¥30 雷达辐射源信号参考模型
  • ¥15 html+css+js如何实现这样子的效果?
  • ¥15 STM32单片机自主设计
  • ¥15 如何在node.js中或者java中给wav格式的音频编码成sil格式呢
  • ¥15 不小心不正规的开发公司导致不给我们y码,
  • ¥15 我的代码无法在vc++中运行呀,错误很多
  • ¥50 求一个win系统下运行的可自动抓取arm64架构deb安装包和其依赖包的软件。
  • ¥60 fail to initialize keyboard hotkeys through kernel.0000000000