单元测试基础
目的
- 减少Bug:如今的项目大多都是多人分模块协同开发,当各个模块集成时再去发现问题,定位和沟通成本是非常高的,通过单元测试来保证各个模块的正确性,可以尽早的发现问题,而不时等到集成时再发现问题。
- 放心重构:如今持续型的项目越来越多,代码不断的在变化和重构,通过单元测试,开发可以放心的修改重构代码,减少改代码时心理负担,提高重构的成功率。
- 改进设计:越是良好设计的代码,一般越容易编写单元测试,多个小的方法的单测一般比大方法(成百上千行代码)的单测代码要简单、要稳定,一个依赖接口的类一般比依赖具体实现的类容易测试,所以在编写单测的过程中,如果发现单测代码非常难写,一般表明被测试的代码包含了太多的依赖或职责,需要反思代码的合理性,进而推进代码设计的优化,形成正向循环。
特征
原则
- 自动化(A: Automatic)
- 独立性(I: Independent)
- 可重复(R: Repeatable)
- 可维护(M: Maintainability)
要求
- 关注重要的业务逻辑和边界情况
- 自动化、可重复执行
- 通过断言进行结果检测,而不是人工检查
- 任何人都应该能一键运行它
- 结果应该是稳定的
- 运行速度应该很快
- 能完全控制被测试的单元
- 完全隔离(独立于其他测试的运行)
可维护性
- 单元测试用例之间决不能互相调用
- 不能依赖执行的先后次序
- 单元测试是可以重复执行的,不能受到外界环境的影响
- 单元测试通常会被放到持续集成中,每次代码有check in时单元测试都会被执行
- 如果对外部环境(网络,服务,中间件等)有依赖,容易导致集成机制不可用
- 为了不受外界环境的影响,要求设计代码时就把SUT的依赖改成注入
- 在测试时用spring这样的DI框架注入一个本地(内存)实现或者Mock实现
- 对于单元测试,要保证测试粒度足够小,有助于精确定位问题,单元测试粒度至多是类级别,通常是方法级别的
- 只有测试粒度小才能在出错时尽快定位到出错位置
- 单元测试不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域
- 核心业务,核心应用,核心模块的增量代码确保单元测试通过
- 新增代码及时补充单元测试
- 如果新增代码影响了原有代码,确保及时修正
- 单元测试代码必须写在如下工程目录中 :src/test/java, 不允许写在业务代码目录下
- 源码构建会跳过此目录,而单元测试框架默认扫描此目录
内容
- 接口功能测试:用来保证接口功能的正确性。
- 局部数据结构测试(不常用):用来保证接口中的数据结构是正确的
- 边界条件测试
- 变量没有赋值(即为NULL)
- 变量是数值(或字符)
- 主要边界:最小值,最大值,无穷大(对于DOUBLE等)
- 溢出边界(期望异常或拒绝服务):最小值-1,最大值+1
- 临近边界:最小值+1,最大值-1
- 变量是字符串
- 引用“字符变量”的边界
- 空字符串
- 对字符串长度应用“数值变量”的边界
- 变量是集合
- 空集合
- 对集合的大小应用“数值变量”的边界
- 调整次序:升序、降序
- 变量有规律
- 比如对于Math.sqrt,给出 和的边界
- 所有独立执行通路测试:保证每一条代码,每个分支都经过测试
- 代码覆盖率
- 语句覆盖:保证每一个语句都执行到了
- 判定覆盖(分支覆盖):保证每一个分支都执行到
- 条件覆盖:保证每一个条件都覆盖到true和false(即if、while中的条件语句)
- 路径覆盖:保证每一个路径都覆盖到
- 相关软件
- Cobertura:语句覆盖
- Emma: Eclipse插件Eclemma
- 各条错误处理通路测试:保证每一个异常都经过测试
TDD测试驱动