设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

构建一个可测试的Go Web应用

2014-10-15 12:05| 发布者: joejoe0332| 查看: 3049| 评论: 0|原作者: 鑫鑫鑫|来自: oschina

摘要: 这篇文章中,我们将讨论如何设计 Sourcegraph的单元测试,使其简单易写,容易维护,运行快速并可以被其他人使用。我们希望这里提到的一些模式有助于其他写Go web app的人,同时欢迎对于我们测试方法的建议。在开始测 ...


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()
    // define the routes
    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()
    // mount handlers
    m.Get(RepoGetRoute).HandlerFunc(handleRepoGet)
    http.Handle("/api/", m)
}

 而http.Handlers 实际上在API服务器包中挂载:

1
2
3
4
5
6
func init() {
    m := NewAPIRouter()
    // mount handlers
    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") }
}


高级测试目标回顾

使用上述模式,我们实现了测试目标。我们的代码是:

  • 目标明确: 一次测试一层。

  • 全面: 三个应用层均被测试。

  • 快速: 测试运行得很快。

  • DRY: 我们合并了三个应用层的通用接口, 在应用代码和测试中进行了重用。

  • 易模仿: 一个仿制实现在三个应用层中都可以使用,想测试以Sourcegraph为基础构建的库的外部API用户也可以使用。

关于如何重新构建并改进Sourcegraph的测试的故事就讲完了。这些模式和例子在我们的环境中运行良好,我们希望这些模式和例子也能帮助到Go社区的其他人,显而易见的是它们并不是在每一个场景下都是正确的,我们确信还有改进的空间。我们在不断的尝试改进做事的方法,所以我们乐意听到你的建议和反馈——说说你用Go写测试的经历吧!


酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部