更改处理程序时httptestserver中的争用条件



我有一部分测试,我想在httptest的一个实例上运行它们。服务器每个测试都有自己的处理程序函数。

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"运行时,此代码会给出一个比赛。这可能是因为服务器在goroutine中运行,而我正试图同时更改一个处理程序。我说得对吗?

如果我尝试为每个测试创建一个新服务器的替代代码,那么就没有竞争:

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

所以,第一个问题是,在没有竞争的情况下,使用一台服务器进行一部分测试和动态更改处理程序的最佳方式是什么?就性能而言,拥有一台服务器而不是创建新服务器值得吗?

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变量。由于多路复用器处理程序是从另一个goroutine调用的,因此必须同步对该变量的访问(因为我们正在向其写入,而服务器的goroutine读取它(。

最新更新