测 HTTP handler,可以直接调用(ResponseRecorder),也可以起一个真的服务器(httptest.Server)。两者看到的请求、返回的响应并不一样,有些 bug 只在其中一种里出现。这篇并排比较,并把 Go 1.27.1 里新的内存网络版 NewTestServer 也跑了一遍。
ResponseRecorder:Code=200 Proto=HTTP/1.1 Content-Length="" Content-Type="text/plain; charset=utf-8" 响应头个数=1 httptest.Server:Code=200 Proto=HTTP/1.1 Content-Length="100" Content-Type="text/plain; charset=utf-8" 响应头个数=3
同一个 handler,Recorder 的响应只有 Content-Type,真实服务器多了 Content-Length: 100 和另一个头(应当是 Date,没有逐个打印)。检查响应头的测试要想清楚用哪种。
NewRequest + Recorder:RemoteAddr=192.0.2.1:1234 Host=example.com Proto=HTTP/1.1 TLS=false BodyNil=false httptest.Server:RemoteAddr=127.0.0.1:58998 Host=127.0.0.1:58997 Proto=HTTP/1.1 TLS=false BodyNil=false httptest.NewRequest 的 Body == http.NoBody:true;自己 http.NewRequest 不给 body 时 Body 是 <nil>
httptest.NewRequest 造出来的请求 RemoteAddr 是 192.0.2.1:1234、Host 是 example.com,Body 是 http.NoBody;真实服务器看到的是实际的回环地址和端口。依赖 RemoteAddr 的代码(限流、日志)两种方式下取值不同。
Recorder:WriteHeader(200) 再 WriteHeader(500) 之后 Code = 200,Body = "x",没有任何错误或日志
Recorder 不会提示重复 WriteHeader,Code 就是第一次的值,没有任何日志;真实服务器会在日志里打印 superfluous。
Recorder:handler 里的 panic 直接传给了测试代码(没有被 recover):handler panic httptest.Server:handler panic 时客户端得到 err = Get "http://127.0.0.1:58999": EOF
Recorder 直接调用 handler,panic 会传进测试代码,测试会因此失败;真实服务器会 recover 并关掉连接,客户端只看到 EOF。如果你的 handler 依赖“panic 会被服务器兜住”,用 Recorder 测不出来。
httptest.NewRequest("GET", "://bad", nil) → panic:invalid NewRequest arguments; parse "://bad": missing protocol scheme
http.NewRequest("GET", "://bad", nil) → 返回 error:parse "://bad": missing protocol schemehttptest.NewRequest 遇到非法 URL 直接 panic(测试代码里很方便),http.NewRequest 返回 error。
httptest.NewTestServer:ts.URL = "http://example.com";ts.Listener == nil:true;handler 看到 RemoteAddr=192.0.2.1:10001 Host=example.com Proto=HTTP/1.1 TLS=false BodyNil=false 用 ts.Client() 访问任意地址 http://anything.invalid/other/path 也能到达这个服务器:状态 200,handler 看到 RemoteAddr=192.0.2.1:10002 Host=anything.invalid Proto=HTTP/1.1 TLS=false BodyNil=false
Go 1.27.1 里的 httptest.NewTestServer(t, handler) 默认用内存网络:没有真实端口(Listener 为 nil),ts.URL 是 http://example.com,必须用 ts.Client() 发请求,请求任意地址都会到达它(实验里用 anything.invalid 也行)。我没有查它是哪个 Go 版本引入的。
Recorder 直接调用 handler:1509 ns/次(20 次分配);httptest.NewServer(真实回环端口)+ 客户端往返:45998 ns/次(63 次分配);httptest.NewTestServer(内存网络)+ ts.Client() 往返:13320 ns/次(63 次分配)
Recorder 约 1.5 µs,内存网络的 NewTestServer 往返约 13.3 µs,真实回环端口约 46 µs。需要经过完整 HTTP 往返但不想占端口、也想快一点时,NewTestServer 比 NewServer 快三倍多。
NewTestServer returns a new Server for a test. The server will use an in-memory network implementation by default. If the handler is nil, the server will serve 500 responses to all requests. It will not use http.DefaultServeMux. NewRequest wraps NewRequestWithContext using context.Background.
文档说 NewTestServer 在 handler 为 nil 时对所有请求返回 500,并且不会使用 http.DefaultServeMux。