在我个人职业生涯的前两年,我几乎没有写过几个正规的测试用例,大部分时候都是赶进度用人工测试,当然所在的团队规模也不是特别大,所有的测试都是 developer 自己完成。那个时候我对测试虽然稍有存疑,但还是抱着想试试的态度,当时摆在我面前的问题是:测什么和如何测。
随着日后经历的团队规模不断变大,开发的软件服务的复杂度和用户量的不断变高,对测试的理解也从不知如何想入手到持续推进自动化和 CI 在团队中不断普及和应用,甚至强行将测试覆盖率和测试质量纳为开发人员的考核指标,在此我们一起好好聊聊软件生命质量的保证:测试。
为什么很多 developer 不写测试,甚至厌恶写测试
不想测试的 developer 常常由自己的一套理由或者借口。
观点一:我的代码跑得很好,为什么要测试
对这样的观点最有力的回应是,你怎么知道你的代码跑得好好的,你测试过么,除非你现在测试一下你的代码证明它跑得很正确。但事实上自动化测试带来的好处绝对是显而易见的:
- 可重复执行,有利于代码反复修改和迭代
- 可随时发布,让你的代码第一时间接触用户
- 节省时间
……
wecatch 开源的 app-turbo 在上线到生产环境第一个版本之后(彼时测试覆盖率不足20%),每迭代一个小版本大概需要一周时间,当测试覆盖率达到 30% 已经可以 3 天发一个小版本,现在 50% 的覆盖率如果不是大功能变动,一天发可以一个版本。
观点二:测试会让开发周期变长
测试就像是练习长跑,一开始身体肯定会非常不适应,但跑步这件事从更长远的时间来看对健康是有益的。同样,测试在软件开发的早期会花费不短的时间,但如果你能坚持并不断地改进它,让它最终变成软件的一部分,它能让你的软件得到持续的改进,质量不断提高,还能帮助你节省很多时间。
大部分 developer 不愿意测试,都是觉得测试让开发周期变长,这个周期他们一般都指创作软件时花费的时间,而事实上占用整个软件生命周期最长的部分是维护,把测试放到这个尺度上来衡量它占用的时间其实是很可观的。
观点三:不知道测试什么
万事开头难,如果真的准备要测试你写的代码,下面这些意见值得参考。
- 从你的一个函数开始,最好这个函数不依赖任何外部系统,包括数据库、网络等,这样的测试就是单元测试
- 测试你的软件最复杂的部分,然后持续改进这部分代码,你会马上从自动化测试中受益。
- 任何时候如果你发现了一个 bug,在修复之前写一个测试用来来覆盖它,来验证你确实修复了这个问题
- 当你觉得写新的代码毫无头绪时,去写一个测试用例吧,既能让你暂时摆脱眼前的思考,还能打发时间
没有任何测试经验,如何实践
对于一个来到测试世界的新人来说,确实很难知道从哪里开始测试,对于刚接触测试,推荐了解和实践 :
- Unit testing 单元测试
- Integration testing 集成测试
- Regression testing 回归测试
Unit testing
如何定义 unit testing?如果你要做的测试:
- 与数据库产生交互
- 与网络产生交互
- 使用了文件系统
- 不能和其他单元测试一起执行
- 需要特殊的运行环境保障测试的执行
这些测试都不能算是 unit testing,unit testing 要保证自身可以在不依赖任何第三方系统的情况下完成测试,尤其针对提供单一功能的函数或类。
Unit testing 是最基础最简单的测试单元,输入输出都非常容易,几乎没有依赖,不需要搭建复杂的外部系统就可以实践和验收。
为了让 unit testing 编写容易、测试准确,测试对象一定要具备良好的可测试性,衡量可测性最常见的标准有:
- 函数职责单一
- 单一的输入,必然产生单一的输出,不具有二义性
可见测试反过来可作用于代码的设计和组织,逼迫 developer 输出更优质的代码。
入门实践 unit testing 可从现有代码中不与外部依赖的单一的 function 和 class 开始,逐步改进和增强。
Integration testing
集成测试是面向体系的测试,要求必须要有准确的外部环境依赖来保证数据的正确输入和交互,集成测试要求 developer 能对代码的工作流程了如指掌,并且能正确模拟各个模块的输入和输出以达到合并和组织单元模块让以验证它们一起工作是否表现良好的目的。
集成测试需要更多的数据准备和输入,而且要求数据之间的逻辑关系定义明确,所以开始集成测试前一定要:
- 准备你的测试环境,比如硬件资源、数据库依赖等
- 准备你的测试数据,明确数据间的关系定义,并且能够重复输入,比如 SQL 语句、某些表的数据
- 组合代码的工作流程,让其可以满足验证不同的逻辑分支
Regression testing
回归测试是在单元测试和集成测试的基础上,对已有测试的重复验证,其目的是为了保证新的修改不会对旧功能造成破坏。
回归测需要注意的重要的一点是一定要界定回归的范围,把握和控制不同的修改造成的影响以及是否需要回归。
这些介绍已经足够入门和实践了,首先为你的代码最复杂的部分写测试,越是复杂的部分越是能从自动化测试中受益,即使只是使用最简单的测试工具完成你的工作。
完整测试的生命周期
很多主流语言都自带了测试框架或工具用来完成测试有关的繁重工作,一般来讲对于一个应用来说,按照代码作用域划分都会有几种不同范围的测试。
- package test | module test
- function test
- class test
- API test
按照这几种维度,测试框架一般会提供不同阶段的 hook 用来完成测试所需要数据和环境的准备与销毁工作:
1 | # setup |
而测试的所有工作就是围绕这些控制生命周期的各种 hook 展开的,所以学习和使用他们是开展测试的第一步工作。
推荐阅读
- http://pythontesting.net/books/pytest
- Test-Driven Development with Python
- Lessons Learned in Software Testing: A Context-Driven Approach
- Beautiful Testing: Leading Professionals Reveal How They Improve Software
- Testing Computer Software
- Agile Testing: A Practical Guide for Testers and Agile Teams
- 10 个良好的测试习惯