单元测试,测试驱动开发
1. 简介 下图出自 Learning Curves (for different programming languages) 虽然文章调侃为主, 但也看出来作者对单元测试的态度。 对Python程序员来讲,随着经验的增长, 在掌握了单元测试之后, 个人的生产力得到一个突变(至于掌握装饰器,会自我感觉膨胀,但效率提升不明显)。 很多年前刚走出校门,还是C++年代。 有个前北电的Java大牛布道,给开发团队推荐cpp-unit,说单元测试 “可以显著提高单兵作战能力”。斗转星移,南征北战,北电的Eddie已失去联系,但他的布道显然成功了。我在不同的项目中实践过单元测试: cpp unit junit luaunit python unittest jest 虽然语言不同,但unittest思想都一样,都有setup,teardown,testcase,assert/expect,mock这些概念。 对单元测试“提高单兵作战能力”的说法,我“不能认同更多”。 2. 单元测试的好处 unittest的好处主要在两个方面。 2.1. 鼓励先设计接口 要测试驱动,就要先想怎么测,从而促进在很早期就从接口定义的角度考虑问题。也促进了模块的低耦合高内聚。 另外,还带来一个额外的好处,TDD也会沉淀出类似文档的测试用例。若干年后回顾一个软件时候,看看用例,基本也了解当时的思路了。 2.2. 跑一遍测试的成本低 测试用例的写法规范,测试框架支持方便的执行和反馈结果,这样跑一遍测试没有时间和精力的负担,随时跑。 经常跑测试,持续的集成,有问题也能早暴露。 接口稳定后,有测试用例做质量保障,做重构也方便。 3. 举个例子 假如在开发一个API服务器。 做单元测试从粗到细可以有几个不同的粒度: 从API层面,用http client模拟请求,然后assert返回的结果是否符合预期 从Handler层面,模拟http请求的header,params,body等,然后喂给Handler,看返回的结构是否符合预期 模块层面,如果Handler之下还有其它业务逻辑模块,可以针对模块接口做单元测试 以上 1, 已经可以看作系统测试(端到端测试)了。 2相对于1有一些额外的好处:一般情况下,对应一个请求的处理,除了Hanlder之外,还有一些中间件来做预处理。 对于1来说,必须把中间件的功能和Handler本身作为一个整体测试。 不够灵活。 2相对于1又有一些缺点,需要Mock。 根据所用framework不同,需要mock输入输出的数据结构。 好在一般情况下,mock都比较简单,有很多framework也有第三方做好的mock库。 例如,下面对JS express 的Handler测试: import { posthandler } from '../src/handlers/post'; import { getMockReq, getMockRes } from '@jest-mock/express'; // generate a mocked response and next function, with provided values const { res, next } = getMockRes({ }) test('check post handler returns token in JSON body', async () => { // generate a mock request with params const req = getMockReq({ params: { id: 'abc-def' }, headers:{authorization:'this is my token'} }) // provide the mock req, res, and next to assert await posthandler(req, res) expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ authorization: 'this is my token', }), ) }) 引入的第三方jest-mock/express,可以帮助来生成输入数据,检查输出数据。see? easy. ...