iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >如何构建一个可测试的Go Web应用
  • 184
分享到

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

2024-04-02 19:04:59 184人浏览 八月长安
摘要

如何构建一个可测试的Go WEB应用,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。几乎每一个程序员都赞同测试是重要的,但测试以多种方式让写

如何构建一个可测试Go WEB应用,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

几乎每一个程序员都赞同测试是重要的,但测试以多种方式让写测试的人员打退堂鼓。它们可能运行慢,可能使用重复的代码,可能一次测试得太多导致难以定位测试失败的根源。

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

框架

和其他web app一样,我们的网站有三层:

当一个用户请求Sourcegraph的页面,前端收到HTTP页面请求,并对API服务器发起一系列HTTP请求。 然后API服务器开始查询数据存储, 数据存储将数据返回给API服务器,然后编码成 jsON格式,返回给web前端服务器,前端使用Go html/template包将数据显示并格式化成HTML。

框架图如下:(更多细节,查看 recap of our Google I/O talk about building a large-scale code search engine in Go.)

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

测试 v0

当我们***次开始构建Sourcegraph,我们以最容易跑起来的方式写了测试。每一个测试都将进入数据库对测试API端点发起HTTP GET请求。测试会解析HTTP返回内容并和预期数据进行对比。一个典型的v0测试如下:

func TestListRepositories(t *testing.T) {    tests := []struct { url string; insert []interface{}; want []*Repo }{      {"/repos", []*Repo{{Name: "foo"}}, []*Repo{{Name: "foo"}}},      {"/repos?lang=Go", []*Repo{{Lang: "python"}}, nil},      {"/repos?lang=Go", []*Repo{{Lang: "Go"}}, []*Repo{{Lang: "Go"}}},    }    db.Connect()    s := http.NewServeMux()    s.Handle("/", router)    for _, test := range tests {      func() {        req, _ := http.NewRequest("GET", test.url, nil)        tx, _ := db.DB.DbMap.Begin()        defer tx.Rollback()        tx.Insert(test.data...)        rw := httptest.NewRecorder()        rw.Body = new(bytes.Buffer)        s.ServeHTTP(rw, req)        var got []*Repo        json.NewDecoder(rw.Body).Decode(&got)        if !reflect.DeepEqual(got, want) {          t.Errorf("%s: got %v, want %v", test.url, got, test.want)        }      }()    }  }

一开始这么写测试简单易行,但随着app进化会变得痛苦。 随着时间推移,我们加入了新特性。更多的特性导致更多的测试,更长的运行时间,延长了我们的dev周期。更多的特性也需要改变和添加新的URL路径(现在大概有75个),大都相当复杂。 Sourcegraph的每一层内部也变得更加复杂,所以我们想独立于其他层做测试。

我们在测试当中遇到了一些问题:

1.测试慢,因为他们要和实际的数据库互动——插入测试用例,发起查询,回滚每一次测试事务。每一次测试大约运行100毫秒,随着我们添加更多的测试累加。

2.测试难以重构。测试用字符串写死了HTTP路径和查询的参数,这意味着如果我们想改变一个URL路径或者查询参数集,不得不手动更新测试中的URL。这种痛会随着我们的URL路由复杂度和数量的增长而加剧。

3.有大量的散乱脆弱的样本代码。安装每一个测试要求确保数据库运行正常并拥有正确的数据。这样的代码在多个案例中重复使用,但是差异的足以在安装代码中引入bug。我们发现自己花大量的时间调试我们的测试而非实际的app代码。

4.测试失败难以诊断。随着app变得更加复杂,因为每一个测试都访问三个应用层,测试失败的根源难以诊断。我们的测试比起单元测试更像是整合测试。

***,我们提出了开发一个公开发行的API客户端的需求。我们想让API容易被模仿,以便于我们的API用户也可以写出好测的代码。

高级测试目标:

随着我们的app演进,我们意识到需要能满足这些高要求的测试:

  • 目标明确:我们需要单独测试app的每一层。

  • 全面: 我们app的全部三层都要被测试到。

  • 快速: 测试需要运行的非常快,意味着不再进行数据库互动。

  • DRY: 尽管我们的app每一层都不同,它们共享了许多通用的数据结构。测试需要利用这一点去消除重复的样本代码。

  • 易模仿: API外部用户应当也可以使用我们的内部测试模式。以我们的API为基础构建的工程,应当可以容易地写出良好的测试。 毕竟,我们的web前端不是独特的——它只是另一个API用户。

我们如何重建测试

写良好的、可维护的测试和良好的、可维护的应用代码是密不可分的。重构应用代码使我们可以极大地改进我们的测试代码,这是我们改进测试的步骤。

1. 构建一个Go HTTP API 客户端

简化测试的***步是用Go为我们的API写一个高质量的客户端。之前,我们的网站是angularJS app,但是因为我们主要服务静态内容,我们决定将前端HTML生成移动到服务器。这么做以后,我们的新前端就可以使用Go的API客户端和API服务器通信。我们的客户端go-sourcegraph是开源的,go-GitHub库对它的影响巨大。客户端代码(特别是获取仓库数据(repository data)的端点代码)如下:

func NewClient() *Client {    c := &Client{BaseURL:DefaultBaseURL}    c.Repositories = &repoService{c}    return c  }     type repoService struct{ c *Client }     func (c *repoService) Get(name string) (*Repo, error) {      resp, err := http.Get(fmt.Sprintf("%s/api/repos/%s", c.BaseURL, name))      if err != nil {          return nil, err      }      defer resp.Body.Close()      var repo Repo      return &repo, json.NewDecoder(resp.Body).Decode(&repo)  }

以前,我们的v0 API测试把大量的URL路径和构建好的HTTP请求用ad-hoc的方式写死,现在它们可以使用这个API客户端构建和发起请求了。

2. 统一HTTP API客户端和数据仓库的接口

接下来,我们统一HTTP API和数据仓库的接口。以前我们的API http.Handlers直接发起SQL查询。现在我们的API http.Handlers只需要解析http.Request再调用我们的数据仓库,数据仓库和HTTP API客户端实现了一样的接口。

借鉴上面的HTTP API客户端(*repoService).Get的方法,我们现在也有了(*repoStore).Get:

func NewDatastore(dbh modl.SqlExecutor) *Datastore {    s := &Datastore{dbh: dbh}    s.Repositories = &repoStore{s}    return s  }     type repoStore struct{ *Datastore }     func (s *repoStore) Get(name string) (*Repo, error) {      var repo *Repo      return repo, s.db.Select(&repo, "SELECT * FROM repo WHERE name=$1", name)  }

统一这些接口把我们的web app的行为描述放在一个地方,使得它更易理解和推理。而且我们可以在API客户端和数据仓库中重用相同的数据类型和参数结构。

3. 集中URL路径定义

之前,我们不得不在应用的多个层重新定义URL路径。在API客户端中,我们的代码是这样的

resp, err := http.Get(fmt.Sprintf("%s/api/repos/%s", c.BaseURL, name))

这种方式很容易引发错误,因为我们有超过75个路径定义,还有很多是复杂的。集中URL路径定义意味着从API服务器独立出来在一个新包中重构路径。路径包中声明了路径的定义。

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服务器包中挂载:

func init() {      m := NewAPIRouter()      // mount handlers      m.Get(RepoGetRoute).HandlerFunc(handleRepoGet)      http.Handle("/api/", m)  }

现在我们可以在API客户端中使用路径包生成URL,而不是把它们写死。(*repoService).Get方法现在如下:

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查询。失败难以诊断,测试也很慢。

现在,我们拥有每一层的独立测试并且我们模仿了毗邻层的功能。因为应用的每一层实现了相同的接口,所以我们可以在所有的三层中使用同样的仿制接口。

仿制的实现是简单的模拟函数结构,可以在每一个测试中指明:

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。(这段代码使用了上述所有方法。)

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 Web应用问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注编程网JavaScript频道了解更多相关知识。

--结束END--

本文标题: 如何构建一个可测试的Go Web应用

本文链接: http://www.lsjlt.com/news/86216.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何构建一个可测试的Go Web应用
    如何构建一个可测试的Go Web应用,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。几乎每一个程序员都赞同测试是重要的,但测试以多种方式让写...
    99+
    2024-04-02
  • 如何用GIN构建一个WEB服务
    这篇文章主要介绍“如何用GIN构建一个WEB服务”,在日常操作中,相信很多人在如何用GIN构建一个WEB服务问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何用GIN构建一个WEB服务”的疑惑有所帮助!接下来...
    99+
    2023-06-26
  • 如何使用Go和Laravel构建Web应用程序?
    随着Web应用程序的不断发展,越来越多的开发者开始使用多种技术来构建高效、可靠的应用程序。在本文中,我们将介绍如何使用Go和Laravel构建Web应用程序,让你快速入门,学习如何构建高质量的应用程序。 什么是Go和Laravel? Go是...
    99+
    2023-08-16
    laravel 学习笔记 shell
  • cypress如何测试本地web应用
    这篇文章主要介绍“cypress如何测试本地web应用”,在日常操作中,相信很多人在cypress如何测试本地web应用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”cypress如何测试本地web应用”的疑...
    99+
    2023-06-30
  • 用Go和Django构建Web应用程序:哪一个更容易学习?
    在当今时代,Web应用程序的需求越来越大,因此开发人员需要选择一种易于学习且能够快速构建Web应用程序的技术。在本文中,我们将比较使用Go和Django构建Web应用程序的学习难度和开发效率。我们还将提供一些演示代码,以帮助您更好地理解这两...
    99+
    2023-09-14
    django git laravel
  • 如何使用 Go 函数和 Django 在 Linux 上构建高可用性的 Web 应用程序?
    随着互联网的不断发展,Web 应用程序已经成为了现代化社会生活中必不可少的一部分。而如何构建高可用性的 Web 应用程序则成为了一个非常重要的话题。本文将介绍如何使用 Go 函数和 Django 在 Linux 上构建高可用性的 Web 应...
    99+
    2023-07-09
    函数 linux django
  • 发挥Go语言优势,构建高效可扩展的Web应用
    Go语言作为一种高效、并发性强的编程语言,逐渐在Web开发领域中崭露头角。本文将探讨如何发挥Go语言的优势,构建高效可扩展的Web应用,并提供具体的代码示例。 在构建Web应用时,我们...
    99+
    2024-02-22
    go语言 高效 可扩展 并发请求 封装性 标准库
  • Java RESTful API 的实战应用:构建一个动态的 Web 应用程序
    简介 RESTful API(Representational State Transfer API)是一种遵循 REST(Representational State Transfer)原则的 Web 服务接口,用于在客户端和服务器之...
    99+
    2024-03-07
    Java RESTful API、Web 应用程序、动态 Web、REST API、SpringBoot、Spring MVC
  • 如何使用Go和Bash构建一个具有高可用性的数组框架?
    在现代的计算机应用程序中,数组是最基本的数据结构之一。然而,由于数组的重要性,我们需要确保数组框架具有高可用性,以确保应用程序的稳定性和可靠性。本文将介绍如何使用Go和Bash构建一个具有高可用性的数组框架。 一、什么是高可用性 高可用性...
    99+
    2023-11-05
    bash 数组 框架
  • 如何使用Node.js构建一个简单Web服务器
    Node.js是一个用于后端服务的JavaScript运行环境,它允许开发者使用同一种语言编写服务器端和客户端应用程序。与其他后端技术相比,Node.js具有更高的处理能力和更好的可扩展性。在这篇文章中,我们将介绍如何使用Node.js构建...
    99+
    2023-05-14
  • 如何使用Go构建Kubernetes应用
    这篇文章主要介绍“如何使用Go构建Kubernetes应用”,在日常操作中,相信很多人在如何使用Go构建Kubernetes应用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用Go构建Kubernete...
    99+
    2023-06-15
  • OAuth如何构建一个即时消息应用
    这篇“OAuth如何构建一个即时消息应用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“OAuth如何构建一个即时消息应用”文...
    99+
    2023-06-16
  • “如何使用Go语言和Laravel构建高效的Web应用程序?”
    如何使用Go语言和Laravel构建高效的Web应用程序? Web应用程序开发是一个繁重的任务,需要考虑许多因素,包括性能、可维护性、易用性等等。在这篇文章中,我们将介绍如何使用Go语言和Laravel框架来构建高效的Web应用程序。我们将...
    99+
    2023-09-08
    npm laravel numpy
  • Java、Linux、Apache 和 Django:如何构建高可用性的 Web 应用程序?
    随着 Web 应用程序越来越流行,如何构建高可用性的 Web 应用程序成为了开发人员和企业关注的焦点。在这篇文章中,我们将介绍如何使用 Java、Linux、Apache 和 Django 来构建高可用性的 Web 应用程序,并且会穿插一...
    99+
    2023-08-23
    linux apache django
  • 使用Spring MVC 和Mybatis 如何构建一个高性能的web
    这期内容当中小编将会给大家带来有关使用Spring MVC 和Mybatis 如何构建一个高性能的web,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。项目结构:是一个典型的Maven 项目 :src/ma...
    99+
    2023-05-31
    spring springmvc mybatis
  • FastApi如何快速构建一个web项目的实现
    目录项目介绍项目目录构成项目内容数据文件内容模板渲染同步接口异步接口项目入口文件项目依赖项目部署访问效果FastApi快速构建一个web项目 已经使用FastApi很久了。这个一个...
    99+
    2023-03-24
    FastApi构建web项目 FastApi构建web
  • NPM、Django、PHP,哪一个在构建可扩展的 Web 应用程序时表现更好?
    随着 Web 应用程序的不断发展,越来越多的开发人员开始关注可扩展性。在选择技术栈时,NPM、Django 和 PHP 是三个备受关注的选项。那么,哪一个在构建可扩展的 Web 应用程序时表现更好呢? NPM NPM 是一个 Node.js...
    99+
    2023-08-28
    npm django load
  • Java和Django:哪一个更适合构建Web应用程序?
    随着互联网的快速发展,Web应用程序的需求量也在逐年增长。Java和Django是两种非常流行的Web应用程序开发框架,它们各自拥有自己的优点和缺点。在这篇文章中,我们将讨论Java和Django这两种框架的特点,以及它们在Web应用程序...
    99+
    2023-11-12
    django http git
  • 函数式编程如何在golang中构建可测试的代码?
    函数式编程在 go 中增强可测试性:纯函数不会修改输入或外部状态,保证恒定的结果输出,便于测试。不可变数据结构防止测试期间数据的修改,提高测试的可靠性。函数式编程实践可重写 maxmin...
    99+
    2024-05-01
    函数式编程 可测试代码 golang
  • 如何利用Python和NPM快速构建可扩展的Web应用程序?
    Web应用程序是如今互联网世界中的重要组成部分。而Python和NPM分别是构建Web应用程序的两个最流行的工具。Python是一种高级编程语言,非常适合构建Web应用程序,而NPM则是JavaScript包管理器,可以帮助您管理Web应...
    99+
    2023-08-08
    apache npm javascript
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作