浅谈单元测试
- 单元测试场景
- 单元测试粒度
- 外部依赖
- 单元测试中是不允许有任何外部依赖的,也就是说这些外部依赖都需要被模拟(mock)。外部依赖越多,mock越复杂。如何用模拟的依赖来测试真实依赖的行为?mock写的太简单,达不到测试的目的。mock太复杂, 不仅成本增加,而且又如何确保mock的正确性呢?
- 有的时候模拟是有效的方便的。但是其他一些时候,过多的模拟对象,Stub对象,假对象,导致单元测试主要在测模拟对象而不是实际的系统。
- Costs and Benefits
- 在受益于单元测试的好处的同时,也必然增加了代码量以及维护成本(单元测试代码也是要维护的)。下面这张成本/价值象限图很清晰的阐述了在不同性质的系统中单元测试__成本__和__价值__之间的关系。
-
1.依赖很少的简单的代码(左下)
对于外部依赖少,代码又简单的代码。自然其成本和价值都是比较低的。举Go官方库里errors包为例,整个包就两个方法New()和Error(),没有任何外部依赖,代码也很简单,所以其单元测试起来也是相当方便。
2. 依赖较多但是很简单的代码(右下)
依赖一多,mock和stub就必然增多,单元测试的成本也就随之增加。但代码又如此简单(比如上述errors包的例子),这个时候写单元测试的成本已经大于其价值,还不如不写单元测试。
3. 依赖很少的复杂代码 (左上)
像这一类代码,是最有价值写单元测试的。比如一些独立的复杂算法(银行利息计算,保险费率计算,TCP协议解析等),像这一类代码外部依赖很少,但却很容易出错,如果没有单元测试,几乎不能保证代码质量。
4.依赖很多又很复杂(右上)
这种代码显然是单元测试的噩梦。写单元测试吧,代价高昂;不写单元测试吧,风险太高。像这种代码我们尽量在设计上将其分为两部分:1.处理复杂的逻辑部分 2.处理依赖部分
-
我们遇到最常见的依赖无非下面几种:
- 网络依赖——函数执行依赖于网络请求,比如第三方http-api,rpc服务,消息队列等等
- 数据库依赖
- I/O依赖(文件)
- 未开发完成的功能模块
- 处理方式:抽象成接口,通过mock和stub进行模拟测试
- 开始敲产品代码的时候,我们必然已经过初步的设计,已经了解系统中的外部依赖以及业务复杂的部分,这些部分是要优先考虑写单元测试的
- 单元测试的工具
- Mockito
- Mockito是一个针对Java的mocking框架。它与EasyMock和jMock很相似,但是通过在执行后校验什么已经被调用,它消除了对期望行为(expectations)的需要。其它的mocking库需要你在执行前记录期望行为(expectations),而这导致了丑陋的初始化代码。
- Mockito
- 案例分析