我有两个 Go 测试函数,其中一个的执行会导致另一个失败。
我很清楚,标准 http 库保存了一些全局变量或状态,这些变量或状态在多次测试中仍然存在。对于此类情况,建议具有一些清理功能。不过我认为测试通常应该不会互相影响。这有点违背了重点。
最小可重复样品
main_test.go
:
package main
import (
"net/http"
"testing"
"time"
)
func start_server(t *testing.T) {
err := http.ListenAndServe(":8090", nil)
if err != nil {
t.Errorf("server shut down: %s\n", err.Error())
}
}
func TestA(t *testing.T) {
go start_server(t)
timer := time.NewTimer(time.Second)
<-timer.C
}
func TestB(t *testing.T) {
go start_server(t)
timer := time.NewTimer(time.Second)
<-timer.C
}
运行这两个测试:
$ go test -v main_test.go
=== RUN TestA
--- PASS: TestA (1.00s)
=== RUN TestB
main_test.go:12: server shut down: listen tcp :8090: bind: address already in use
--- FAIL: TestB (1.00s)
FAIL
FAIL command-line-arguments 2.005s
FAIL
但是,当仅执行第二个测试时:
$ go test -v main_test.go -run TestB
=== RUN TestB
--- PASS: TestB (1.00s)
PASS
ok command-line-arguments 1.006s
我的问题是,如何使测试完全独立于其他测试执行?并行化不是必需的。
net/http/httptest
是最好的路线。
它不是一个“模拟”服务器——它将进行路由匹配、运行处理程序等等。它只是不会绑定到网络地址来执行此操作。
如果您确实需要适合您的用例的网络服务,请创建您自己的
net.Listener
而不是使用http.ListenAndServe
。这使您可以绑定到随机的、未分配的端口并避免测试之间的冲突。
您的示例启动了一个运行
TestA
但从未关闭的服务器。当 TestB
尝试绑定到同一端口时,它无法绑定。遵循以下方法之一将解决该问题:
func startServerTest(t testing.TB) *http.Client {
t.Helper()
// Add your handler here
srv := httptest.NewServer(nil)
// Close the server whenever the test/sub-test finishes
t.Cleanup(func() {
srv.Close()
})
return srv.Client()
}
func TestA(t *testing.T) {
client := startServerTest(t)
url := "..."
_, err := client.Get(url)
if err != nil {
t.Errorf("could not issue request: %v", err)
}
}
func startServerReal(t testing.TB) string {
t.Helper()
// port '0' means find any available port
//
// Use lis.Addr() to access the socket that was bound
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Errorf("could not start server: %v", err)
return ""
}
// Close the server whenever the test/sub-test finishes
t.Cleanup(func() {
lis.Close()
})
// Add your handler here
go http.Serve(lis, nil)
return lis.Addr().String()
}
func TestB(t *testing.T) {
addr := startServerReal(t)
url := url.URL{
Scheme: "http",
Host: addr,
Path: "/",
}
_, err := http.Get(url.String())
if err != nil {
t.Errorf("could not issue request: %v", err)
}
}