知识点
net/http/httptest
在标准库中有一个 net/http/httptest
包,它可以让你轻易建立一个 HTTP 模拟服务器(mock HTTP server)。
我们改为使用模拟测试,这样我们就可以控制可靠的服务器来测试了。
func TestRacer(t *testing.T) {
slowServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(20 * time.Millisecond)
w.WriteHeader(http.StatusOK)
}))
fastServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
slowURL := slowServer.URL
fastURL := fastServer.URL
want := fastURL
got := Racer(slowURL, fastURL)
if got != want {
t.Errorf("got '%s', want '%s'", got, want)
}
slowServer.Close()
fastServer.Close()
}
httptest.NewServer
接受一个我们传入的 匿名函数 http.HandlerFunc
。
http.HandlerFunc
是一个看起来类似这样的类型:type HandlerFunc func(ResponseWriter, *Request)
。
这些只是说它是一个需要接受一个 ResponseWriter
和 Request
参数的函数,这对于 HTTP 服务器来说并不奇怪。
结果呢,这里并没有什么彩蛋,这也是如何在 Go 语言写一个 真实的 HTTP 服务器的方法。唯一的区别就是我们把它封装成一个易于测试的 httptest.NewServer
,它会找一个可监听的端口,然后测试完你就可以关闭它了。
我们让两个服务器中慢的那一个短暂地 time.Sleep
一段时间,当我们请求时让它比另一个慢一些。然后两个服务器都会通过 w.WriteHeader(http.StatusOK)
返回一个 OK
给调用者。
defer
在某个函数调用前加上 defer
前缀会在 包含它的函数结束时 调用它。
有时你需要清理资源,例如关闭一个文件,在我们的案例中是关闭一个服务器,使它不再监听一个端口。
你想让它在函数结束时执行(关闭服务器),但要把它放在你创建服务器语句附近,以便函数内后面的代码仍可以使用这个服务器。
进程同步
示例代码:
func Racer(a, b string) (winner string, error error) {
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(10 * time.Second):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
}
func ping(url string) chan bool {
ch := make(chan bool)
go func() {
http.Get(url)
ch <- true
}()
return ch
}
(一)ping
我们定义了一个可以创建 chan bool
类型并返回它的 ping
函数。
在这个案例中,我们并不 关心 channel 中发送的类型, 我们只是想发送一个信号 来说明已经发送完了,所以返回 bool 就可以了。
同样在这个函数中,当我们完成 http.Get(url)
时启动了一个用来给 channel 发送信号的 Go 程(goroutine)。
(二)select
如果你记得并发那一章的内容,你可以通过 myVar := <-ch
来等待值发送给 channel。这是一个 阻塞 的调用,因为你需要等待值返回。
select
则允许你同时在 多个 channel 等待。第一个发送值的 channel「胜出」,case
中的代码会被执行。
我们在 select
中使用 ping
为两个 URL
设置两个 channel。无论哪个先写入其 channel 都会使 select
里的代码先被执行,这会导致那个 URL
先被返回(胜出)。
如此一来,进程同步实现起来非常简单。
引用
欢迎大家关注我的公众号
半亩房顶
网友评论