一个 Golang 项目的测试实践全记录

桔妹导读:最近有一个项目,链路涉及了4个服务。最核心的是一个配时服务。要如何对这个项目进行测试,保证输出质量,是最近思考和实践的重点。这篇就说下最近这个实践的过程总结。

测试金字塔

按照Mike Cohn提出的“测试金字塔”概念,测试分为4个层次:

最下面是单元测试,单元测试对代码进行测试。再而上是集成测试,它对一个服务的接口进行测试。继而是端到端的测试,我也称他为链路测试,它负责从一个链路的入口输入测试用例,验证输出的系统的结果。再上一层是我们最常用的UI测试,就是测试人员在UI界面上根据功能进行点击测试。

单元测试

对于一个Golang写的服务,单元测试已经是很方便了。我们在写一个文件,函数的时候,可以直接在需要单元测试的文件旁边增加一个_test.go的文件。而后直接使用 go test 直接跑测试用例就可以了。

一般单元测试,最直接的衡量标准就是代码覆盖率。单元测试一般测试的对象是一个函数,一个类。这个部分已经有很多实践例子了,就补在赘述。

集成测试

思考和需求

对于一个服务,会提供多个接口,那么,测试这些接口的表现就是集成测试最重要的目标了。只有通过了集成测试,我们的这个服务才算是有保障。

手头这个配时项目,对外提供的是一系列HTTP服务,基本上代码是以MVC的形式架构的。在思考对它的集成测试过程中,我希望最终能做到下面几点:

首先,我希望我手上这个配时服务的集成测试是自动化的。最理想的情况下,我能调用一个命令,直接将所有case都跑一遍。

其次,衡量集成测试的达标指标。这个纠结过一段时间,是否需要有衡量指标呢?还是直接所有case通过就行?我们的服务,输入比较复杂,并不是简单的1-2个参数,是一个比较复杂的json。那么这个json的构造有各种各样的。需要实现写一些case,但是怎么保证我的这些case是不是有漏的呢?这里还是需要有个衡量指标的,最终我还是选择用代码覆盖率来衡量我的测试达标情况,但是这个代码覆盖率在MVC中,我并不强制要求所有层的所有代码都要覆盖住,主要是针对Controller层的代码。controller层主要是负责流程控制的,需要保证所有流程分支都能走到。

然后,我希望集成测试中有完善的测试概念,主要是TestCase, TestSuite,这里参考了JUnit的一些概念。TestCase是一个测试用例,它提供测试用例启动和关闭时候的注入函数,TestSuite是一个测试套件,代表的是一系列类似的测试用例集合,它也带测试套件启动和关闭时候的注入函数。

最后,可视化需求。我希望这个测试结果很友好,能有一个可视化的测试界面,我能很方便知道哪个测试套件,哪个测试用例中的哪个断言失败了。

集成测试实践

Golang 只有test.go的测试,其中的每个TestXXX相当于是TestCase的概念,也没有提供测试case启动,关闭执行的注入函数,也没有TestSuite的概念。首先我需要使用 Golang 的test搭建一个测试架子。

集成测试和单元测试不一样,它不属于某个文件,集成测试可能涉及到多个文件中多个接口的测试,所以它需要有一个单独的文件夹。它的目录结构我是这么设计的:

suites

存放测试套件

suites/xxx

这里存放测试套件,测试套件文件夹需要包含下列文件:

before.go 存放有

  • SetUp() 函数,这个函数在Suite运行之前会运行
  • Before() 函数,这个函数在所有Case运行之前运行

after.go 存放有

  • TearDown() 函数,这个函数在Suite运行之后会运行
  • After() 函数,这个函数在Suite运行之后运行

run_test.go 文件

这个文件是testsuite的入口,代码如下:

  1. package adapt

  2. import "testing"
  3. import . "github.com/smartystreets/goconvey/convey"

  4. func TestRunSuite(t *testing.T) {
  5. SetUp()
  6. defer TearDown()
  7. Convey("初始化", t, nil)

  8. runCase(t, NormalCasePEE001)
  9. runCase(t, PENormalCase01)
  10. runCase(t, PENormalCase04)
  11. runCase(t, PENormalCase11)
  12. runCase(t, PENormalCase13)
  13. runCase(t, PENormalCase14)
  14. runCase(t, NormalCasePIE001)
  15. runCase(t, NormalCasePIE002)
  16. runCase(t, NormalCase01)
  17. runCase(t, NormalCase02)
  18. runCase(t, NormalCase07)
  19. runCase(t, NormalCase08)
  20. runCase(t, NormalCasePIN003)
  21. runCase(t, NormalCasePIN005)
  22. runCase(t, NormalCasePIN006)
  23. runCase(t, NormalCasePIN015)

  24. }

  25. func runCase(t *testing.T, testCase func(*testing.T)) {
  26. Before()
  27. defer After()

  28. testCase(t)
  29. }

envionment

初始化测试环境的工具

当前我这里面存放了初始化环境的配置文件和db的建表文件。

report

存放报告的地址

代码覆盖率需要额外跑脚本

在tester目录下运行:sh coverage.sh 会在report下生成coverage.out和coverage.html,并自动打开浏览器。

引入 Convey

关于可视化的需求。

我引入了Convey这个项目,http://goconvey.co/ 。第一次看到这个项目,觉得这个项目的脑洞真大。

下面可了劲的夸一夸这个项目的优点:

断言

首先它提供了基于原装 go test 的断言框架;提供了 Convey 和 So 两个重要的关键字,还提供了 Shouldxxx 等一系列很好用的方法。它的测试用例写下来像是这个样子:

package package_name
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestIntegerStuff(t *testing.T) {
Convey("Given some integer with a starting value", t, func() {
x := 1
Convey("When the integer is incremented", func() {
x++
Convey("The value should be greater by one", func() {
So(x, ShouldEqual, 2)
})
})
})
}

很清晰明了,并且超赞的是很多参数都使用函数封装起来了,go中的 := 和 = 的问题能很好避免了。并且不要再绞尽脑汁思考tmp1,tmp2这种参数命名了。(因为都已经分散到Convey语句的func中了)

Web界面

其次,它提供了一个很赞的Web平台,这个web平台有几个点我非常喜欢。首先它有一个case编辑器。

什么叫好的测试用例实践? 我认为这个编辑器完全体现出来了。写一个完整的case先考虑流程和断言,生成代码框架,然后我们再去代码框架中填写具体的逻辑。这种实践步骤很好解决了之前写测试用例思想偷懒的问题,特别是断言,基本不会由于偷懒而少写。

其次它提供很赞的测试用例结果显示页面:

很赞吧,哪个case错误,哪个断言问题,都很清楚显示出来。

还有,goconvey能监控你运行测试用例的目录,当目录中有任何文件改动的时候,都会重新跑测试用例,并且提供提醒

这个真是太方便了,可以在每次保存的时候,都知道当前写的case是否有问题,能直接提高测试用例编写的效率。

TestSuite初始化

Web服务测试的环境是个很大问题。特别是DB依赖,这里不同的人有不同的做法。有使用model mock的,有使用db的。这里我的经验是:集成测试尽量使用真是DB,但是这个DB应该是私有的,不应该是多个人共用一个DB。

所以我的做法,把需要初始化的DB结构使用sql文件导出,放在目录中。这样,每个人想要跑这一套测试用例,只需要搭建一个mysql数据库,倒入sql文件,就可以搭建好数据库环境了。其他的初始化数据等都在TestSuite初始化的SetUp函数中调用。

关于保存测试数据环境,我这里有个小贴士,在SetUp函数中实现 清空数据库+初始化数据库 ,在TearDown函数中不做任何事情。这样如果你要单独运行某个TestSuite,能保持最后的测试数据环境,有助于我们进行测试数据环境测试。

TestCase 编写

在集成测试环境中,TestCase编写调用HTTP请求就是使用正常的 httptest包,其使用方式没有什么特别的。

代码覆盖率

goconvey有个小问题,测试覆盖率是根据运行goconvey的目录计算的,不能额外设置,但是go test是提供的。所以代码覆盖率我还额外写了一个shell脚本。#

  1. #!/bin/bash

  2. go test -coverpkg xxx/controllers/... -coverprofile=report/coverage.out ./...
  3. go tool cover -html=report/coverage.out -o report/coverage.html
  4. open report/coverage.html

主要就是使用converpkg参数,把代码覆盖率限制在controller层。

继承测试总结

这套搭建实践下来,对接口的代码测试有底很多了,也测试出不少controller层面的bug

端到端测试

这个是测试金字塔的第二层了。

关于端到端的测试,我的理解就是全链路测试。从整个项目角度来看,它属于一个架构的层次了,需要对每个服务有一定的改造和设计。这个测试需要保证的是整个链路流转是按照预期的。

比如我的项目的链路通过了4个服务,一个请求可能在多个服务之间进行链路调用。但是这个项目特别的是,这些服务并不都是一个语言的。如何进行测试呢?

理想的端到端测试我的设想是这样的,测试人员通过 postman 调用最上游的服务,构造不同的请求参数和 case,有的 case 其实可能无法通到最下游,那么就需要有一个全链路日志监控系统,在这个系统可以看到这个请求在各个服务中的流转情况。全链路日志监控系统定义了一套 tag 和一个 traceid,要求所有服务在打日志的时候带上这个 traceid,和当前步骤的 tag,日志监控系统根据这些日志,在页面上能很好反馈出这个链路。

然后测试人员每个case,就根据返回的 traceid,去日志中查找,并且确认链路中的 tag 是否都全齐。

关于如何在各个服务中传递 traceid,这个很多微服务监控的项目中都已经说过了,我也是一样的做法,在 http 的 header 头中增加这个 traceId。

关于打日志的地方,其实有很多地方都可以打日志,但是我只建议在失败的地方+请求的地方打上 tag 日志,并且是由调用方进行 tag 日志记录,这样主要是能把请求和返回都记录,方便调试,查错等问题。

UI 测试

这个目前还是让测试人员手动进行点击。这种方式看起来确实比较low,但是貌似也是目前大部分互联网公司的测试方法了

总结

这几周主要是在集成测试方面做了一些实践,有一些想法和思路,所以拿出来进行了分享,肯定还有很多不成熟的地方没有考虑到,欢迎评论留言讨论。

测试是一个费时费力的工作,大多数情况下,业务的迭代速度估计都不允许做很详细的测试。但是对于复杂,重要的业务,强烈建议这四层的测试都能做到,这样代码上线才能有所底气。

END

叶 剑 锋
滴滴 | 专家工程师

网名ID,轩脉刃,长期维护技术博客:轩脉刃的刀光剑影。2012年开始接触Go语言。一直坚持技术服务于业务的观点,在业务开发中不断总结和分享有益于加速和稳定业务输出的技术研究。

Image placeholder
Vestin
未设置
  52人点赞

没有讨论,发表一下自己的看法吧

推荐文章
零基础学测试 2 - 进一步理解 Laravel 的测试与 PHP Unit 的关系

细心的读者可以发现,上一讲中创建的用例继承的是PHPUnit的测试基类。

项目管理最佳实践,企业如何进行有效的项目管理

前言:企业在划分项目时,可按照项目的复杂程度、管理范围等将项目分为三个级别,分别是企业级、部门级和小组级(与目标划分原则相同),然后将每一级的目标与项目对应起来。我们知道,企业制定的目标(OKR),一

Go 解决国内下载 go get golang.org/x 包失败

GOPROXY环境变量 我们知道从Go1.11版本开始,官方支持了gomodule包依赖管理工具。其实还新增了GOPROXY环境变量。如果设置了该变量,下载源代码时将会通过这个环境变量设置的代理地址,

MySQL 亿级数据数据库优化方案测试-银行交易流水记录的查询

作者:逸宸a链接:https://www.jianshu.com/p/cbdef47fb837对MySQL的性能和亿级数据的处理方法思考,以及分库分表到底该如何做,在什么场景比较合适?比如银行交易流水

Go 切片 slice - Go 学习记录

Go切片slice slice切片类型基本概念 slice总是指向一个底层的array,slice的声明也可以像array一样,只是不需要定义长度 slice并不是真正意义上的动态数组,而是一个引用

《MongoDB高手课》学习记录(第十六天)

第十六天今天开始事务的学习,要学习的章节为《19|事务开发:写操作事务》,其实主要就是讲一个参数writeConcern。什么是writeConcern?之前第一章我们学过,生产环境中MongoDB最

《MongoDB高手课》学习记录(第十七天)

写在前面最近有新项目上线,实在太忙了,学习的进度有点拖沓,但会坚持。第十七天今天要学的是《20|事务开发:读操作事务之一》章节。主要讲解的是怎么哪里读取数据的问题。readRreference参数值有

Testin用iTestin开启下一代测试,测试行业为什么要“重新来过”?

测试,其实并不是一个新话题。从有软件开发开始,就有测试,最早的测试就是找Bug。后来,自动化测试、云测试、众包测试的模式开始成为流行趋势,今天又迎来以智能化为核心的下一代测试。但是,“测试”从简单的软

Go编程语言教程_1.2. 如何在MacOS上安装Golang?

之前,我们从在系统上安装Golang的过程开始。我们必须对Go语言是什么以及它实际上是什么有第一手的了解。Go是Google的RobertGriesemer,RobPike和KenThompson于2

Go编程语言教程_1.3. Golang的Hello World

你好,世界!是任何编程语言中的第一个基本程序。让我们使用以下步骤用Go语言编写第一个程序: 首先打开Go编译器。在Go语言中,该程序以.go扩展名保存,它是UTF-8文本文件。 现在,首先在程序中添加

Go编程语言教程_2.1. Golang中的关键字

关键字或保留字是用于某些内部过程或表示某些预定义动作的语言中的字。因此,不允许将这些单词用作标识符。这样做会导致编译时错误。 例: //Go程序说明 //使用关键字 packagemain impor

一个基于 golang 的爬虫电影站

GoMovies 一个基于golang的爬虫电影站,效果站:https://go-movies.hezhizheng.com/ Github:https://github.com/hezhizheng

GoWeb教程_11.3. Go 怎么写测试用例

开发程序其中很重要的一点是测试,我们如何保证代码的质量,如何保证每个函数是可运行,运行结果是正确的,又如何保证写出来的代码性能是好的,我们知道单元测试的重点在于发现程序设计或实现的逻辑错误,使问题及早

Mac 下使用 pecl 安装 PHP 的 swoole 扩展实践

一、背景前段时间把Mac系统重装了,PHP的一些扩展都没了,昨天需要调试一个swoole开发的项目,发现命令行中的PHP是系统自带的,如果安装swoole扩展很不方便;需要自己手动去下载swoole的

springDataJpa 最佳实践

springDataJpa最佳实践 前言 SpringDataJpa框架的目标是显著减少实现各种持久性存储的数据访问层所需的样板代码量。SpringDataJpa存储库抽象中的中央接口是Reposit

做银行家里的数据专家:ING探索大数据时代下的金融最佳实践

大数据文摘出品记者:高延6月18-21日,O’ReillyAIConference在北京召开。大会上,来自荷兰的金融公司ING的IT主管BasGeerdink带来了《关于数字驱动企业》的主题分享。进入

中信银行信用卡业务数据库实现国产替换,湖北银行新核心系统项目正式验收,阿里云与MongoDB达成战略合作

中信银行信用卡业务数据库实现国产替换10月31日,由IT168旗下ChinaUnix社区主办的第十一届中国系统架构师大会(SACC2019)在北京召开。会上,中信银行软件开发中心/技术平台开发处副处长

Golang语言的主要特性与发展的环境和影响因素

1.2.1影响Go语言发展的早期编程语言 正如“21世纪的C语言”这句话所说,Go语言并不是凭空而造的,而是和C++、Java和C#一样属于C系。不仅如此,设计者们还汲取了其它编程语言的精粹部分融入

Golang 操作 Redis 的基本方法

摘要 看到有位老哥写了PHP操作Redis的基本方法,于是就有了这篇博客 超喜欢Redis的,感觉使用起来很方便,但是社区不温不火哈? 就当是为Redis社区做贡献吧,如果大家喜欢记得点个赞哦 话不多

Golang 里的 AES、DES、3DES 加解密,支持 ECB、CBC 等多种模式组合

Opensslencryption:OpenSSL库的功能包装,用于对称和非对称加密和解密。 AES-ECB AES-CBC DES-ECB DES-CBC 3DES-ECB 3DES-CBC 安

【Golang+MySQL】记一次 MySQL 数据库迁移(一)

【Golang+mysql】记一次mysql数据库迁移(一)文章地址:https://github.com/stayfoo/stayfoo-hub一、准备目标: 腾讯云CVM自建mysql数据迁移到腾

在 Golang 中使用 Protobuf

本教程使用proto3版本的protocolbuffer语言,提供了一个基本的在Go程序中使用protocolbuffer的介绍。通过创建一个简单的示例应用程序,向你展示如何在.proto文件中定义消

GoldenDB ,一个已经全面支撑银行核心系统的国产数据库

摘要:沿用、并存还是替代,一直是银行核心系统数据库转型重点思考的问题。四大行目前主要采用的是沿用与并存的数据库产品战略,在确保稳定的大前提下对新兴数据库技术进行探索研究和实践。相对而言,股份制银行在这

GoWeb教程_11.0. 错误处理,调试和测试

我们经常会看到很多程序员大部分的"编程"时间都花费在检查bug和修复bug上。无论你是在编写修改代码还是重构系统,几乎都是花费大量的时间在进行故障排除和测试,外界都觉得我们程序员是设计师,能够把一个系

Spring-SpringAOP原理,手写Spring事务框架

一、Spring核心知识Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由RodJohnson在其著作ExpertOne-On-OneJ2EEDevelopm