Golang项目的测试实践
最近有一个项目,链路涉及了4个服务。最核心的是一个配时服务。要如何对这个项目进行测试,保证输出质量,是最近思考和实践的重点。这篇就说下最近这个实践的过程总结。html
测试金字塔
按照Mike Cohn提出的“测试金字塔”概念,测试分为4个层次mysql
最下面是单元测试,单元测试对代码进行测试。再而上是集成测试,它对一个服务的接口进行测试。继而是端到端的测试,我也称他为链路测试,它负责从一个链路的入口输入测试用例,验证输出的系统的结果。再上一层是咱们最经常使用的UI测试,就是测试人员在UI界面上根据功能进行点击测试。git
单元测试
对于一个Golang写的服务,单元测试已是很方便了。咱们在写一个文件,函数的时候,能够直接在须要单元测试的文件旁边增长一个_test.go的文件。然后直接使用 go test
直接跑测试用例就能够了。github
通常单元测试,最直接的衡量标准就是代码覆盖率。web
单元测试通常测试的对象是一个函数,一个类。sql
这个部分已经有不少实践例子了,就没什么好聊的。shell
集成测试
思考和需求
对于一个服务,会提供多个接口,那么,测试这些接口的表现就是集成测试最重要的目标了。只有经过了集成测试,咱们的这个服务才算是有保障。数据库
手头这个配时项目,对外提供的是一系列HTTP服务,基本上代码是以MVC的形式架构的。在思考对它的集成测试过程当中,我但愿最终能作到下面几点:json
首先,我但愿我手上这个配时服务的集成测试是自动化的。最理想的状况下,我能调用一个命令,直接将全部case都跑一遍。浏览器
其次,衡量集成测试的达标指标。这个纠结过一段时间,是否须要有衡量指标呢?仍是直接全部case经过就行?咱们的服务,输入比较复杂,并非简单的1-2个参数,是一个比较复杂的json。那么这个json的构造有各类各样的。须要实现写一些case,可是怎么保证个人这些case是否是有漏的呢?这里仍是须要有个衡量指标的,最终我仍是选择用代码覆盖率来衡量个人测试达标状况,可是这个代码覆盖率在MVC中,我并不强制要求全部层的全部代码都要覆盖住,主要是针对Controller层的代码。controller层主要是负责流程控制的,须要保证全部流程分支都能走到。
而后,我但愿集成测试中有完善的测试概念,主要是TestCase, TestSuite,这里参考了JUnit的一些概念。TestCase是一个测试用例,它提供测试用例启动和关闭时候的注入函数,TestSuite是一个测试套件,表明的是一系列相似的测试用例集合,它也带测试套件启动和关闭时候的注入函数。
最后,可视化需求。我但愿这个测试结果很友好,能有一个可视化的测试界面,我能很方便知道哪一个测试套件,哪一个测试用例中的哪一个断言失败了。
集成测试实践
Golang 只有_test.go的测试,其中的每一个Test_XXX至关因而TestCase的概念,也没有提供测试case启动,关闭执行的注入函数,也没有TestSuite的概念。首先我须要使用 Golang 的test搭建一个测试架子。
集成测试和单元测试不同,它不属于某个文件,集成测试可能涉及到多个文件中多个接口的测试,因此它须要有一个单独的文件夹。它的目录结构我是这么设计的:
suites
存放测试套件
suites/xxx
这里存放测试套件,测试套件文件夹须要包含下列文件:
before.go存放有
- SetUp() 函数,这个函数在Suite运行以前会运行
- Before() 函数,这个函数在全部Case运行以前运行
after.go存放有
- TearDown() 函数,这个函数在Suite运行以后会运行
- After() 函数,这个函数在Suite运行以后运行
run_test.go文件
这个文件是testsuite的入口,代码以下:
package adapt import "testing" import . "github.com/smartystreets/goconvey/convey" func TestRunSuite(t *testing.T) { SetUp() defer TearDown() Convey("初始化", t, nil) runCase(t, NormalCasePEE001) runCase(t, PENormalCase01) runCase(t, PENormalCase04) runCase(t, PENormalCase11) runCase(t, PENormalCase13) runCase(t, PENormalCase14) runCase(t, NormalCasePIE001) runCase(t, NormalCasePIE002) runCase(t, NormalCase01) runCase(t, NormalCase02) runCase(t, NormalCase07) runCase(t, NormalCase08) runCase(t, NormalCasePIN003) runCase(t, NormalCasePIN005) runCase(t, NormalCasePIN006) runCase(t, NormalCasePIN015) } func runCase(t *testing.T, testCase func(*testing.T)) { Before() defer After() testCase(t) }
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脚本
#!/bin/bash go test -coverpkg xxx/controllers/... -coverprofile=report/coverage.out ./... go tool cover -html=report/coverage.out -o report/coverage.html 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,可是貌似也是目前大部分互联网公司的测试方法了。
总结
这几周主要是在集成测试方面作了一些实践,有一些想法和思路,因此拿出来进行了分享,确定还有不少不成熟的地方没有考虑到,欢迎评论留言讨论。
测试是一个费时费力的工做,大多数状况下,业务的迭代速度估计都不容许作很详细的测试。可是对于复杂,重要的业务,强烈建议这四层的测试都能作到,这样代码上线才能有所底气。