Go语言:编写一个 WebsiteRacer 的函数,用来对比请求两个 URL 来「比赛」,并返回先响应的 URL。如果两个 URL 在 10 秒内都未返回结果,返回一个 error。
Posted slowlydance2me
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言:编写一个 WebsiteRacer 的函数,用来对比请求两个 URL 来「比赛」,并返回先响应的 URL。如果两个 URL 在 10 秒内都未返回结果,返回一个 error。相关的知识,希望对你有一定的参考价值。
问题:
你被要求编写一个叫做 WebsiteRacer
的函数,用来对比请求两个 URL 来「比赛」,并返回先响应的 URL。如果两个 URL 在 10 秒内都未返回结果,那么应该返回一个 error
。
实现这个功能我们需要用到
-
net/http
用来调用 HTTP 请求
-
net/http/httptest
用来测试这些请求
-
Go 程(goroutines)
-
select
先写测试
我们从最幼稚的做法开头把事情开展起来。
func TestRacer(t *testing.T) slowURL := "http://www.facebook.com" fastURL := "http://www.quii.co.uk" want := fastURL got := Racer(slowURL, fastURL) if got != want t.Errorf("got \'%s\', want \'%s\'", got, want)
我们知道这样不完美并且有问题,但这样可以把事情开展起来。重要的是,不要徘徊在第一次就想把事情做到完美。
尝试运行测试
./racer_test.go:14:9: undefined: Racer
为测试的运行编写最少量的代码,并检查失败测试的输出
func Racer(a, b string) (winner string) return
racer_test.go:25: got \'\', want \'http://www.quii.co.uk\'
编写足够的代码使程序通过
func Racer(a, b string) (winner string) startA := time.Now() http.Get(a) aDuration := time.Since(startA) startB := time.Now() http.Get(b) bDuration := time.Since(startB) if aDuration < bDuration return a return b
对每个 URL:
-
1.我们用
time.Now()
来记录请求URL
前的时间。
-
2.然后用
http.Get
来请求URL
的内容。这个函数返回一个http.Response
和一个error
,但目前我们不关心它们的值。
-
3.
time.Since
获取开始时间并返回一个time.Duration
时间差。
我们完成这些后就可以通过对比请求耗时来找出最快的了。
问题
这可能会让你的测试通过,也可能不会。问题是我们通过访问真实网站来测试我们的逻辑。
使用 HTTP 测试代码非常常见,Go 标准库有这类工具可以帮助测试。
在前两章模拟和依赖注入章节中,我们讲到了理想情况下如何不依赖外部服务来进行测试,因为它们可能
-
速度慢
-
不可靠
- 无法进行边界条件测试
在标准库中有一个
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
给调用者。如果你重新运行测试,它现在肯定会通过并且会更快完成。你可以调整 sleep 时间故意破坏测试。
重构
我们在主程序代码和测试代码里都有一些重复。
func Racer(a, b string) (winner string) aDuration := measureResponseTime(a) bDuration := measureResponseTime(b) if aDuration < bDuration return a return b func measureResponseTime(url string) time.Duration start := time.Now() http.Get(url) return time.Since(start)
这样简化代码后可以让 Racer
函数更加易读。
func TestRacer(t *testing.T) slowServer := makeDelayedServer(20 * time.Millisecond) fastServer := makeDelayedServer(0 * time.Millisecond) defer slowServer.Close() defer fastServer.Close() slowURL := slowServer.URL fastURL := fastServer.URL want := fastURL got := Racer(slowURL, fastURL) if got != want t.Errorf("got \'%s\', want \'%s\'", got, want) func makeDelayedServer(delay time.Duration) *httptest.Server return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) time.Sleep(delay) w.WriteHeader(http.StatusOK) ))
我们通过一个名为 makeDelayedServer
的函数重构了模拟服务器,以将一些不感兴趣的代码移出测试并减少了重复代码。
defer
在某个函数调用前加上
defer
前缀会在 包含它的函数结束时 调用它。有时你需要清理资源,例如关闭一个文件,在我们的案例中是关闭一个服务器,使它不再监听一个端口。
你想让它在函数结束时执行(关闭服务器),但要把它放在你创建服务器语句附近,以便函数内后面的代码仍可以使用这个服务器。
我们的重构是一次改进,并且目前是涵盖 Go 语言特性提供的合理解决方案,但我们可以让它更简单。