最新要闻
- 楼市回暖背后:多城二手房在售量持续增加
- 环球新消息丨vivo将整合旗下iQOO手机:开启降本增效
- 王传福:比亚迪目标年底成为中国最大汽车制造商
- 全球热讯:全球最大游戏展危险了!世嘉、腾讯均宣布不参加本届E3
- 绿巨人前女友15年后回归漫威!《美国队长4》新剧照曝光:黑人美队现身
- 环球即时看!联名高达!ROG游戏手机7系列来了:二代骁龙8+6000mAh电池
- 全球视讯!中国代表敦促个别国家立即解除对叙利亚单边制裁
- 天天简讯:四方达(300179)3月28日主力资金净卖出861.62万元
- 微信QQ出现功能异常 官方紧急修复:微信支付等功能已恢复
- 天天速看:男子陵园祭祖车雷达显示全是人 网友:啥车?我想试试
- PS主机神作好评仅有32%!顽皮狗回应《最后生还者》PC优化翻车
- 全球速读:专家谈ChatGPT:或让更多人享受生活 一周只工作一天
- 全球热讯:电池级碳酸锂价格腰斩 电动车会降价吗?专家给出结论
- 环球速看:魔法氛围拉满!Redmi Note 12 Turbo哈利波特版图赏
- 不用带手机 抬手就能付!微信支付尝试推出微信刷掌服务
- 天天要闻:开发者危机!微软GitHub启动裁员:印度工程师团队几乎整体裁撤
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
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 语言特性提供的合理解决方案,但我们可以让它更简单。进程同步
- Go 在并发方面很在行,为什么我们要一个接一个地测试哪个网站更快呢?我们应该能够同时测试两个。
- 我们并不关心请求的 准确响应时间,我们只是需要知道哪个更快返回而已。
select
的新构造(construct),它可以帮我们轻易清晰地实现进程同步。func Racer(a, b string) (winner string) { select { case <-ping(a): return a case <-ping(b): return 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
先被返回(胜出)。做了这些修改后,我们的代码背后的意图就很明确了,实现起来也更简单。超时最后的需求是当 Racer
耗时超过 10 秒时返回一个 error。先写测试
t.Run("returns an error if a server doesn"t respond within 10s", func(t *testing.T) { serverA := makeDelayedServer(11 * time.Second) serverB := makeDelayedServer(12 * time.Second)
defer serverA.Close() defer serverB.Close()
_, err := Racer(serverA.URL, serverB.URL)
if err == nil { t.Error("expected an error but didn"t get one") }})
为了练习这个场景,现在我们要使模拟服务器超过 10 秒后返回两个值,胜出的 URL(这个测试中我们用 _
忽略掉了)和一个 error
。
尝试运行测试
./racer_test.go:37:10: assignment mismatch: 2 variables but 1 values
为测试的运行编写最少量的代码,并检查失败测试的输出
func Racer(a, b string) (winner string, error error) { select { case <-ping(a): return a, nil case <-ping(b): return b, nil }}修改
Racer
的函数签名来返回胜出者和一个 error
。返回 nil
仅用于模拟顺利的场景(happy cases)。编译器会报怨你的 第一个测试只期望一个值,所以把这行改为 got, _ := Racer(slowURL, fastURL)
,要知道顺利的场景中我们不应得到一个 error
。现在运行测试会在超过 11 秒后失败。--- FAIL: TestRacer (12.00s) --- FAIL: TestRacer/returns_an_error_if_a_server_doesn"t_respond_within_10s (12.00s) racer_test.go:40: expected an error but didn"t get one
编写足够的代码使程序通过
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) }}使用
select
时,time.After
是一个很好用的函数。当你监听的 channel 永远不会返回一个值时你可以潜在地编写永远阻塞的代码,尽管在我们的案例中它没有发生。time.After
会在你定义的时间过后发送一个信号给 channel 并返回一个 chan
类型(就像 ping
那样)。对我们来说这完美了;如果 a
或 b
谁胜出就返回谁,但如果测试达到 10 秒,那么 time.After
会发送一个信号并返回一个 error
慢速测试
现在的问题是这个测试要耗时 10 秒以上。对这么简单的逻辑来说可不好。我们可以做的就是让超时时间(timeout)可配置,这样测试就可以设置一个非常短的时间,并且代码在真实环境中可以被设置成 10 秒。func Racer(a, b string, timeout time.Duration) (winner string, error error) { select { case <-ping(a): return a, nil case <-ping(b): return b, nil case <-time.After(timeout): return "", fmt.Errorf("timed out waiting for %s and %s", a, b) }}现在代码不能编译了,因为我们没提供超时时间。在急于将这个默认值添加到测试前,先让我们 聆听他们。
- 在顺利的情况「happy test」下我们是否关心超时时间?
- 需求对超时时间很明确
鉴于以上信息,我们再做一次小的重构来让我们的测试和代码的用户合意
var tenSecondTimeout = 10 * time.Secondfunc Racer(a, b string) (winner string, error error) { return ConfigurableRacer(a, b, tenSecondTimeout)}func ConfigurableRacer(a, b string, timeout time.Duration) (winner string, error error) { select { case <-ping(a): return a, nil case <-ping(b): return b, nil case <-time.After(timeout): return "", fmt.Errorf("timed out waiting for %s and %s", a, b) }}
我们的用户和第一个测试可以使用 Racer
(使用 ConfigurableRacer
),不顺利的场景测试可以使用 ConfigurableRacer
。
func TestRacer(t *testing.T) { t.Run("compares speeds of servers, returning the url of the fastest one", func(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, err := Racer(slowURL, fastURL) if err != nil { t.Fatalf("did not expect an error but got one %v", err) } if got != want { t.Errorf("got "%s", want "%s"", got, want) } }) t.Run("returns an error if a server doesn"t respond within 10s", func(t *testing.T) { server := makeDelayedServer(25 * time.Millisecond) defer server.Close() _, err := ConfigurableRacer(server.URL, server.URL, 20*time.Millisecond) if err == nil { t.Error("expected an error but didn"t get one") } })}
在第一个测试最后加了一个检查来验证我们没得到一个 error
总结
我们学到了什么?
select
- 可帮助你同时在多个 channel 上等待。
- 有时你想在你的某个「案例」中使用
time.After
httptest
- 一种方便地创建测试服务器的方法,这样你就可以进行可靠且可控的测试。
- 使用和
net/http
关键词:
-
Go语言:编写一个 WebsiteRacer 的函数,用来对比请求两个 URL 来「比赛」,并返回先响应的 URL。如果两个 URL 在 10 秒内都未返回结果
问题:你被要求编写一个叫做WebsiteRacer的函数,用来对比请求两个URL来「比赛」,并返回先响应的URL。...
来源: Go语言:编写一个 WebsiteRacer 的函数,用来对比请求两个 URL 来「比赛」,并返回先响应的 URL。如果两个 URL 在 10 秒内都未返回结果
全球实时:一文带你搞懂如何优化慢SQL
焦点快报!Dijkstar-And-Astar算法
用上ChatGPT的这几个功能,你的开发效率不高都难
联想y400什么时候上市的?联想y400笔记本配置
地下城与勇士龙年套装哪个好?地下城与勇士龙年套装有几个宝珠?
HTCG28什么时候上市的?HTCG28手机参数
华为C8813Q如何装sim卡?华为C8813Q手机参数
gprs套餐费是什么意思?怎么关闭GPRS套餐?
楼市回暖背后:多城二手房在售量持续增加
环球关注:编写高质量c#代码的10个建议
环球即时看!插入排序
【世界速看料】【Visual Leak Detector】配置项 StackWalkMethod
环球新消息丨vivo将整合旗下iQOO手机:开启降本增效
王传福:比亚迪目标年底成为中国最大汽车制造商
全球热讯:全球最大游戏展危险了!世嘉、腾讯均宣布不参加本届E3
绿巨人前女友15年后回归漫威!《美国队长4》新剧照曝光:黑人美队现身
环球即时看!联名高达!ROG游戏手机7系列来了:二代骁龙8+6000mAh电池
全球视讯!中国代表敦促个别国家立即解除对叙利亚单边制裁
天天微动态丨KubeVela 1.7 版本解读:接管你的已有工作负载
焦点报道:接通率维持 66% 以上,为什么火山引擎 VeDI 能让企业智能外呼不再难?
【全球快播报】《Python编程快速上手—让繁琐工作自动化》实践项目答案:第四章
天天简讯:四方达(300179)3月28日主力资金净卖出861.62万元
微信QQ出现功能异常 官方紧急修复:微信支付等功能已恢复
天天速看:男子陵园祭祖车雷达显示全是人 网友:啥车?我想试试
PS主机神作好评仅有32%!顽皮狗回应《最后生还者》PC优化翻车
全球速读:专家谈ChatGPT:或让更多人享受生活 一周只工作一天
干货分享|袋鼠云数栈离线开发平台在小文件治理上的探索实践之路
全球热讯:电池级碳酸锂价格腰斩 电动车会降价吗?专家给出结论
环球速看:魔法氛围拉满!Redmi Note 12 Turbo哈利波特版图赏
不用带手机 抬手就能付!微信支付尝试推出微信刷掌服务
天天要闻:开发者危机!微软GitHub启动裁员:印度工程师团队几乎整体裁撤
全球微头条丨同款商超6元/瓶!特仑苏牛奶大促:2.93元/盒相当于半价
讯息:2023华大新高考联盟3月联考各科试题及答案!_老高考老教材
如何隐藏Apache版本号和其它敏感信息
容器安全之 Dockerfile 安全扫描
zookeeper的Leader选举源码解析
焦点要闻:前端设计模式——享元模式
每日观察!使用NSIS打包超大型软件的几个注意事项
环球头条:【手慢无】速来占便宜!三星128GB存储卡仅需49.8元
全球热头条丨汽车雷达在无人陵园内显示全是人影:吓坏车主
985硕士男子失业半年 应聘道士35岁已超龄!做实习生都被拒:现送外卖
没污染!国内首款氢内燃机飞机成功首飞:中国自研
环球新资讯:内地特供的网游性价比神U!酷睿i7-13790F评测:游戏性能强于锐龙9 7950X
环球播报:海豚模拟器登陆Steam
全球实时:半场战报:踢疯了!梅西第38分钟戴帽 阿根廷5-0暂领先库拉索
环球热推荐:Epic独占结束!中国功夫游戏《师父》登陆Steam:139元起
【天天时快讯】Native开发过程中容易忽略的注意事项
全球快播:【国际大宗商品早报】美豆反弹收涨近2% 伊拉克库尔德地区原油出口停顿支撑油价续涨
全固态电池空间电荷层微观机理揭示
环球要闻:核心、显存砍得没法看!RTX 4060 Ti/4060要5月上市:3999元起超值?
天天播报:大作《生化危机4重制版》发售 采用Denuvo正版加密 黑客发声:准备出手破解
天天快看点丨任天堂《塞尔达传说:王国之泪》限定版Switch发布!金黄手柄太酷炫
短讯!《最后生还者》PC版多半差评
每日消息!读SQL进阶教程笔记01_CASE表达式
快讯:成年子女不能与父母在酒店住同一标间引热议 太没道理?酒店回应
Spring整合Mybatis遇到的问题(一)
有关Mongodb 在windows上安装的问题
今头条!JNI知识点总结
天天日报丨【Visual Leak Detector】配置项 ReportTo
【Visual Leak Detector】配置项 SelfTest
环球观察:4个多月第一次!Intel Arc Pro专业显卡终于有了新驱动
当前最新:小米“退钱”了:27万小米空气净化器初代用户 每人899元
全球快报:小金刚手机杀到1999元 卢伟冰喊话友商:欢迎光明磊落竞争
全球播报:人体工学椅从没想过:自己真正的对手会是汽车
天天微资讯!日本海滩惊现大量乌贼尸体 绵延200米
通讯!青石板路图片(青石板规格)
微头条丨喊老公过时了!赵丽颖唐嫣孙俪都这样称呼另一半,网友:甜炸了
项目中如何对XSS统一处理
面向对象设计原则
instanceof的使用
GPT-4救了我狗的命
【全球快播报】你敢坐吗?滴滴能打到自动驾驶汽车了
环球看点!三爱健康集团(01889)发盈喜 预计2022年度股东应占溢利同比增加约223.6%至2922万元
世界头条:用gpt4训练一个简易真人代理
全球热头条丨【Visual Leak Detector】配置项 ReportFile
环球通讯!融创百亿美元境外债务重组成功在即 持债金额超30%的债权人小组已签订重组支持协议
环球新消息丨OpenAI创始人:AI可能毁灭人类 必须开发新技术来解决
焦点速递!美亚柏科:公司将对各类 AIGC 内容的检测、AI 生成文本的检测技术及产品进行布局
聚焦:免费Midjourney AI绘画Prompt提示词平台合集
NCNN 模型推理详解及实战
基于中断的字符串动态显示
全球热推荐:[NOI1999] 生日蛋糕
绿牌将会取消?网友:走好不送
一图看懂Note 12 Turbo:性价比进行到底 16+1TB仅售2599元
一加李杰:用户不会被蒙蔽 将旗舰体验普及到底
视讯!哈迷必备!Redmi Buds 4哈利波特版图赏
首发第二代骁龙7+!Redmi Note 12 Turbo图赏
设备树的概念(四):平台设备驱动和设备树
天天最资讯丨Apache iotdb-web-workbench 认证绕过漏洞(CVE-2023-24829)
Halcon学习教程(一) 之提取十字线中心 图像分割
甲流吃退烧药不退烧怎么办_吃了一粒退烧药多久可以喂奶
环球今日报丨卢伟冰:Note系列全球销量破3.2亿 进入全球单品十强
头条:马斯克为何没做出ChatGPT?揭秘OpenAI创始人的权力斗争
日本推出佛祖版ChatGPT:已经为20多万人解决烦恼
观天下!合资车还咋玩!奇瑞艾瑞泽5 GT上市:起售价仅7.99万
每日快报!Redmi Note 12 Turbo搭载超细四窄边直屏:边框窄至1.42mm!
热消息:一篇文章带你了解面积图
京沪杭等地近期明确将有序放开设摊、允许外摆
全球资讯:德创环保:宁波甬德拟以1.61亿元收购飞乐环保100%股权