在 Gin 中实践TDD测试API
如果这篇文章能够从此让你放下手中的快递员,从此开始做一个 TDD First 的 programmer ,那么本文的目的也就达到了
Why TDD is so important?
TDD 的重要性最直接的体现就是 No guessing 和 Robust,一切都在测试的意料之中,同时也确保你自己考虑清楚了所有的情况。而不是直接将你的代码或API接口
丢给测试人员,然后根据反馈再继续修改,这样一来一回实际上十分降低效率,那么当我们意识到这一点时,为什么我们不能自己先将大部分的BUG利用测试事先发现并改掉呢?
其次,当系统的规模不断变大,新的功能不断加入进来,这时系统的单元测试有利于我们进行系统的重构,通过代码覆盖率定位到死代码(Dead Code),通过查看调用次数将一些
已经非常健壮的代码独立成包或 module,实现增量编译等,在避免臃肿,加快构建速度等改善系统腐化的问题中,测试绝对是功不可没。
TDD can effect code quality
优先编写可测试的类或函数会极大地影响你的代码风格,TDD First 的程序员首先思考的应该是,这个函数的依赖(parameters)是什么?对象实例,基础类型,还是接口?
什么样的函数才是容易测试的?这里就不得不提及 Clean Code 的一句名言
Function should do one thing. They should do it well and do it only
当你愿意主动去写测试的时候,你就越希望这些函数是像这样的,无形之中告诉了你的目标函数的模样。有点抽象,但句句重点。简单来说,一个工程的构建通常都是自底向上的,
越是灵活的系统切分的粒度应当是越细的,就像搭积木一样,从与数据库直接打交道的数据层,到持久化的 service层 ,基于 RESTful 的 Controller 控制层,校验及权限控制,路由层,日志管理
监控等,越高层级的操作越是依赖于低一层级的操作,因此自顶向下对代码健壮性的要求也是越高的。从测试的角度上来看,自底向上看,越是这些细粒度的函数责任越重大,这个层级的测试也是最容易写的,
因为它们正是我们说的那种只做一件事的函数。
TDD decrease BUG
试想一下这两种控制器函数
1 | func (ctrl *UserController) Register(user *entity.User) { |
1 | func (ctrl *UserController) Register(user *entity.User) { |
前一种函数并没有做到 Do one thing,整个函数体十分臃肿,出现 BUG 的可能也越多;第二种控制器函数非常简短,而且基本上在干着一件事,处理错误并将错误响应,倘若校验层和服务层的函数都是经过测试的,
那么在控制器出现问题的可能性又降低了一个数量级
TDD is actually faster
Talk is cheap, show me the code
举一个大多数后端开发人员都要写的 end-to-end 测试,以 gin 框架构建的 API 为例, POST json 然后验证返回的 json
准备工作:
1 | func toReader(t interface{}) *bytes.Reader { |
这些小的通用函数可以封装成测试套件或工具包
对其中一个API接口进行测试:
1 | package api_test |
实际要写的测试用例非常短小,如果用 vscode 的话不用离开编辑器就能运行测试。同时对于较大的系统,也不用等所有路由都装载过后再来测试,大大减少启动的等待时间
将视图层分成 checkpoint 和 endpoint 两层分别表示输入的 json 和 返回的 json 对应的 struct 描述,对于复杂的 input 还能考虑 mock struct,
好处多多,请立即开始你的 TDD 之旅吧!
更多的测试实践可以参考我的示例项目
如有错误,不胜指教。
在 Gin 中实践TDD测试API
