/league
(联盟)的新的端点(endpoint),它可以返回一个玩家清单。她想让它返回一个 JSON 格式的数据。PlayerStore
进行扩充。/league
能否返回一个 OK
的响应。PlayerServer
应该产生一个这样的 panic。通过栈跟踪找到指向 server.go
的那行代码。/league
字符串进行路径分割,因此会报错 slice bounds out of range
(切片超出范围)。x
路径使用 y
handler。/league
被请求时,我们用 http.HandlerFunc
和一个匿名函数来响应 w.WriteHeader(http.StatusOK)
使测试通过。/players/
路由我们只需剪贴代码并粘贴到另一个 http.HandlerFunc
。ServeHTTP
方法处理到来的请求(注意到 ServeMux
也是一个 http.Handler
了吗?)ServeHTTP
看起来很大,我们可以通过重构 handlers 分离成独立的方法。NewPlayerServer
这样的函数,它可以取得依赖并进行一次创建路由的设置。每个请求都可以使用该路由的一个实例。PlayerServer
现在需要储存一个路由。ServeHTTP
路由的动作移到 NewPlayerServer
,这样只需要完成一次,而不是每次请求都要做。PlayerServer{&store}
的地方你需要更新为 NewPlayerServer(&store)
。func (p *PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
,因为不再需要它了。PlayerServer
的第二个属性,删除了命名属性 router http.ServeMux
,并用 http.Handler
替换了它;这被称为 嵌入。Go 没有提供典型的,类型驱动的子类化概念,但它具有通过在结构或接口中嵌入类型来“借用”一部分实现的能力。
PlayerServer
现在已经有了 http.Handler
所有的方法,也就是 ServeHTTP
。http.Handler
,我们将它分配给我们在 NewPlayerServer
中创建的 router
。我们可以这样做是因为 http.ServeMux
具有 ServeHTTP
方法。ServeHTTP
方法,因为我们已经通过嵌入类型公开了它。http.Handler
这个 接口。http.ServeMux
(混合类型),它仍然可以工作 但 PlayerServer
的用户就可以给我们的服务器添加新路由了,因为 Handle(path, handler)
会公开。/league
端点有了一个新的开始。现我我们需要让它返回一些有用的信息。Player
数组,因此我们创建了一个新类型来捕获这个数据模型。Decoder
(解码器),然后调用 encoding/json
包里的 Decode
方法来把 JSON 解析为我们的数据模型。在我们的例子中,Decoder
需要一个 io.Reader
来读取响应体。Decode
取到我们正在尝试解析的东西的地址,这就是为什么我们要在之前的行中声明 Player
的空切片。Decode
可以返回一个 error
。如果失败了,继续测试没有意义,如果发生错误,用 t.Fatalf
停止测试并检查错误。请注意,我们打印了响应正文以及错误,因为对于运行测试的人来说,看看哪些字符串不能被解析很重要。Encoder
,你需要一个 http.ResponseWriter
实现的 io.Writer
。Decoder
,你需要一个 io.Reader
,由我们的响应的 Body
字段实现。io.Writer
,这是它在标准库中流行及许多库如何轻松使用它的另一个示范。leagueTable
之间考虑引入一个拆分是很好的,因为我们知道很快就不会硬编码了。StubPlayerStore
让它存储一个联盟,这只是一个“玩家”的切片类型。我们将存储我们的预期数据。StubPlayerStore
中有了一个新字段;将其设置为 nil 以进行其它测试。StubPlayerStore
里,我们已经把它抽象为 PlayerStore
接口。我们需要更新它以便任何人传入一个 PlayerStore
可以提供联盟的数据。getLeagueTable()
方法,然后更新 leagueHandler
来调用 GetLeague()
。InMemoryPlayerStore
和 StubPlayerStore
没有我们刚添加到接口的新方法。StubPlayerStore
很简单,只要返回我们之前添加的 league
字段即可。InMemoryStore
是如何实现的。GetLeague
非常简单,但记住,我们只是试图 编写最少量的代码来使测试通过。InMemoryStore
的问题。content-type
头(HTTP header),这样机器就能识别出我们正在返回 JSON
。leagueHandler
assertContentType
添加一个辅助函数。PlayerServer
进行了整理,现在我们可以把注意力转向 InMemoryPlayerStore
因为现在如果我们试图给产品负责人演示 /league
将不起作用。/league
获得了正确的响应。t.Run
来分解这个测试,我们可以重用服务器测试中的助手 —— 再次显示重构测试的重要性。GetLeague()
时,InMemoryPlayerStore
返回 nil
,所以我们需要修复它。Player
。http.Handler
接口,因为你可以将路由分配给 Handler
,而路由本身也是 Handler
。它没有你可能期望的某些特性,例如路径变量(例如 /users/{id}
)。你可以自己轻易地解析这些信息,但如果它成了负担,你可能会考虑查看其它路由库。大多数流行的库都坚持标准库的实现 http.Handler
的理念。