3. 集中URL路径定义之前,我们不得不在应用的多个层重新定义URL路径。在API客户端中,我们的代码是这样的 1 | resp, err := http.Get(fmt.Sprintf( "%s/api/repos/%s" , c.BaseURL, name))
|
这种方式很容易引发错误,因为我们有超过75个路径定义,还有很多是复杂的。集中URL路径定义意味着从API服务器独立出来在一个新包中重构路径。路径包中声明了路径的定义。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | const RepoGetRoute = "repo"
func NewAPIRouter() *mux.Router {
m := mux.NewRouter()
m.Path( "/api/repos/{Name:.*}" ).Name(RepoGetRoute)
return m
}
while the http.Handlers were actually mounted in the API server package:
func init() {
m := NewAPIRouter()
m.Get(RepoGetRoute).HandlerFunc(handleRepoGet)
http.Handle( "/api/" , m)
}
|
而http.Handlers 实际上在API服务器包中挂载: 1 2 3 4 5 6 | func init() {
m := NewAPIRouter()
m.Get(RepoGetRoute).HandlerFunc(handleRepoGet)
http.Handle( "/api/" , m)
}
|
现在我们可以在API客户端中使用路径包生成URL,而不是把它们写死。(*repoService).Get方法现在如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 | var apiRouter = NewAPIRouter()
func (s *repoService) Get(name string) (*Repo, error) {
url, _ := apiRouter.Get(RepoGetRoute).URL( "name" , name)
resp, err := http.Get(s.baseURL + url.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
var repo []Repo
return repo, json.NewDecoder(resp.Body).Decode(&repo)
}
|
4. 创建未统一接口的仿制
我们的v0测试同时测试了路径、HTTP处理、SQL生成和DB查询。失败难以诊断,测试也很慢。 现在,我们拥有每一层的独立测试并且我们模仿了毗邻层的功能。因为应用的每一层实现了相同的接口,所以我们可以在所有的三层中使用同样的仿制接口。 仿制的实现是简单的模拟函数结构,可以在每一个测试中指明: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | type MockRepoService struct {
Get_ func(name string) (*Repo, error)
}
var _ RepoInterface = MockRepoService{}
func (s MockRepoService) Get(name string) (*Repo, error) {
if s.Get_ == nil {
return nil, nil
}
return s.Get_(name)
}
func NewMockClient() *Client { return &Client{&MockRepoService{}} }
|
下面是测试中的使用。我们模仿了数据仓库的RepoService,使用HTTP API客户端测试API http.Handler。(这段代码使用了上述所有方法。) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | func TestRepoGet(t *testing.T) {
setup()
defer teardown()
var fetchedRepo bool
mockDatastore.Repo.(*MockRepoService).Get_ = func(name string) (*Repo, error) {
if name != "foo" {
t.Errorf( "want Get %q, got %q" , "foo" , repo.URI)
}
fetchedRepo = true
return &Repo{name}, nil
}
repo, err := mockAPIClient.Repositories.Get( "foo" )
if err != nil { t.Fatal(err) }
if !fetchedRepo { t.Errorf( "!fetchedRepo" ) }
}
|
高级测试目标回顾使用上述模式,我们实现了测试目标。我们的代码是: 关于如何重新构建并改进Sourcegraph的测试的故事就讲完了。这些模式和例子在我们的环境中运行良好,我们希望这些模式和例子也能帮助到Go社区的其他人,显而易见的是它们并不是在每一个场景下都是正确的,我们确信还有改进的空间。我们在不断的尝试改进做事的方法,所以我们乐意听到你的建议和反馈——说说你用Go写测试的经历吧! |