前言
最近在实习开发大项目,百人级别的,流水线工程,质量要求过硬,过硬的前提当然少不了单测。
我前前后后开发了快一个月,代码涉及API和单测,有所心得,所以今天这文章给大家介绍一下单测。
单元测试
测试分为4个层次,单元测试只是第一个层次,见如下的测试金字塔: 分别为:
- 单元测试:对代码进行测试
- 集成测试:对一个服务的接口测试
- 端到端测试(链路测试):从一个链路的入口输入测试用例,验证输出的系统的结果
- UI测试
常犯的错误: 没有断言。没有断言的单测是没有灵魂的.如果只是 print 出结果,单测是没有意义的。 不接入持续集成。单测不应该是本地的 run once ,而应该接入到研发的整个流程中,合并代码,发布上线都应该触发单测执行,并且可以重复执行。 粒度过大。单测粒度应该尽量小,不应该包含过多计算逻辑,尽量只有输入,输出和断言。
单测的特征:
- A:(Automatic,自动化):单元测试应该是全自动执行的,并且非交互式的。
- I:(Independent,独立性):为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
- R:(Repeatable,可重复):单元测试通常会被放到持续集成中,每次有代码 check in 时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。
- 排除外部依赖:一般不允许有任何外部依赖(文件依赖,网络依赖,数据库依赖等),我们不会在测试代码中去连接数据库,调用api等。这些外部依赖在执行测试的时候需要被模拟(mock/stub)
打桩
在单元测试中,通常可以将所涉及的对象分为两种,主要测试对象和次要测试对象。
对于次要测试对象,我们通常只会关注主要测试对象和次要测试对象之间的交互,比如是否被调用、调用参数、调用的次数、调用的结果等,至于次要测试对象是如何执行的,这些细节过程我们并不关注。
我们常常选择使用一个模拟对象来替换次要测试对象,以此来模拟真实场景,对主要测试对象进行测试。而“使用一个模拟对象来替换次要测试对象”这个行为,我们通常称之为“打桩”。因此,“打桩”的作用就是在单元测试中让我们从次要测试对象的繁琐依赖中解脱出来,进而能够聚焦于对主要测试对象的测试上。
Mock和Stub我们不仅可以排除外部依赖,还可以模拟一些异常行为(如数据库服务不可用,没有文件的访问权限等)。
打桩的目的如下:
- 隔离
- 是指将测试任务从产品项目中分离出来,使之能够独立编译、链接,并独立运行。隔离的基本方法就是打桩,将测试任务之外的,并且与测试任务相关的代码,用桩来代替,从而实现分离测试任务。例如函数A调用了函数B,函数B又调用了函数C和D,如果函数B用桩来代替,函数A就可以完全割断与函数C和D的关系。
- 补齐
- 是指用桩来代替未实现的代码,例如,函数A调用了函数B,而函数B由其他程序员编写,且未实现,那么,可以用桩来代替函数B,使函数A能够运行并测试。补齐在并行开发中很常用。
- 控制
- 是指在测试时,人为设定相关代码的行为,使之符合测试需求。
用于实现隔离和补齐的桩函数一般比较简单,只需把原函数的声明拷过来,加一个空的实现,能通过编译链接就行了。 比较复杂的是实现控制功能的桩函数,要根据测试的需要,输出合适的数据
一些经验之谈(字字珠玑)
单测的意义
意义:覆盖你的写的api代码场景
直接目的:为了别人在改你代码逻辑的时候,只需要执行你的单测,如果跑成功了。 即为你本身设计这API的目的没有被修改。
单测的思维
为了更加的合理化,单测也是在另一个方面去规范你写函数,既不可以过长,更不可以冗余错乱。
学会写单测,能在另一个维度去规划你的代码风格。
“写代码的时候别把路走死了”
写单测的思路
最好能够按照语句思维走,分为正确和异常去设计打桩和变量值。
- 注意函数有时候也会影响单测函数(参数被修改),注意单测的严谨性
一些单测技巧
- 有return的地方可以做一次测试。
- 单测的设置/调函数 都要 “打桩单一化”
- 单测实际上都是在“设计”,设计原函数的每一个函数返回值和参数(都是写死的),然后去测每一个函数出口,可以是成功,也可以的异常,也可以是返回值return。
|