You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

110 lines
8.5 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 73 | 软件质量管理:单元测试、持续构建与发布
你好,我是七牛云许式伟。
上一讲 “[72 | 发布单元与版本管理](https://time.geekbang.org/column/article/187641)” 我们聊了版本管理中,只读思想给软件工程带来的确定性价值,它在软件工程质量管理中也是很核心的一点。
## 软件质量管理
今天我们聊聊软件工程中,我们在质量管理上其他方面的一些思考。事实上,软件质量管理横跨了整个软件工程完整的生命周期。
![](https://static001.geekbang.org/resource/image/b8/0e/b86b9e0e6c9185e6993e7cc90175980e.png)
软件工程与传统工程非常不同。它快速变化充满不确定性。不仅如此一个软件工程往往是生命周期以数年甚至数十年计的工程。对于传统工程我们往往把一个工程同时也称之为项目项目工程。但软件工程不同虽然我们平常也有项目的概念但软件工程并不是一个项目而是无数个项目。每个项目只是软件工程中的一个里程碑Milestone
这些都决定了软件工程质量管理的思想与传统工程截然不同。在传统工程中设计的工作往往占比极少重复性的工作占据其生命周期的绝大部分时间。所以传统工程有极大的确定性。检查清单Check List很可能就已经可以很好地实现其工程质量的管理。
但对于软件工程来说设计工作在整个工程中持续发生。哪怕是非设计工作比如编码实现也仍然依赖个体的创造力同样存在较强的不确定性。显然检查清单Check List完全无法满足软件工程的质量管理需要。
那么,到底应该怎么管理软件工程的质量?每次谈软件工程质量保障的时候,我总会先画下面这张图:
![](https://static001.geekbang.org/resource/image/78/e0/78599a7460714c080c8324d83a827fe0.png)
它谈的是软件的生命周期,或者也可以理解为软件中某项功能的生命周期。我们把软件或软件的某项功能生命周期分成两个大的阶段,一个阶段是开发期,一个阶段是维护期。开发期与维护期是相对而言的,只是在表征上,开发期有更强的设计属性。维护期虽然也持续会有设计工作,但是工作量会小一个数量级以上。
为什么划分出开发期与维护期是重要的?
因为开发期的时间跨度虽然可能不长,但是它的影响太大了,基本决定了后期维护期的成本有多高。
这也意味着软件工程是需要有极强预见性的工程。我们在开发期恰如其分地多投入一分精力,后面在维护期就有十倍甚至百倍以上的回报。
设计工作的质量至关重要。但是它执行上又不太有复制性,可复制的只是设计范式和设计思维。
我们只能在这种执行的不确定性中找工程上的确定性。
如何做到?
## 单元测试
首先,做好自动化测试。自动化测试对软件工程的重要性是不言而喻的。如果是一项一次性的工程,我们可以基于常规的手工测试。但常规测试的缺点在于:
其一,一般常规测试是基于手工的,不具备可回归性。因此,常规测试的效率不高,一次完整的测试集跑下来可能需要几天甚至一周之久。
其二,易于缺乏效率,所以往往为了赶工会导致测试仅仅针对典型数据,测试的覆盖率往往也很低。
软件工程的生命周期往往几年甚至几十年之久,我们必然关注单次测试的效率。所以自动化测试的核心价值就在于可回归性与提高测试的覆盖率。
自动化测试与常规测试相比,风格上有很明显的不一样,它有如下重要特征。
* 自动化、可回归性。
* 静默Quiet。没有发生错误的时候就不说话。
* 案例执行的安全受控。某个案例执行的失败,不会影响其他案例的正常运行。
从分类来说,一般自动化测试我们分两个层次:一个是模块级的单元测试,一个是系统级的集成测试。
无论从什么角度来看,模块的单元测试都是重中之重的大事。原因是,单元测试的成本是最低的。
关于测试成本,我们可以从两个维度看。
其一,单元测试的实施成本低,最容易去做。不少高级语言比如 Go 语言甚至在语言内建的工具链上就直接支持。而集成测试虽然也有自动化的方法和支持工具,但是往往需要更高额的代价。
其二减少问题发现的周期进而降低问题的修复成本。单元测试将问题发现周期缩短基本上在问题现场就发现问题这降低了Bug的修复成本。如果问题在系统的集成测试阶段发现那么从问题定位到回忆当初实现这段代码时候的思路到最终去解决掉它必然需要多花费几倍甚至几十倍的时间。
因此,我们鼓励更严格的单元测试要求,更高的单元测试覆盖率,以尽可能把发现问题做到前头。
但仍然有不少公司在推广单元测试上遇到了不小的麻烦,推不起来。
对于这一点,我们认为首先要改变的是对推广单元测试这件事情的认知。我们不把推广单元测试看作是让大家去多做一件额外的事情,而是规范大家做单元测试的方法。
为什么这么说?因为实际上单元测试大家都会去做,很少有人会不经验证就直接交付。但是验证方式上可能有各种 “土” 方法,比如用 print用可视化的界面做输入测试用调试工具做单步跟踪等等。
但是这些方法代价其实一样不低,但是却不可回归,正确与否还需要人脑临时去判断。
更重要的是,这些方法最大的问题是没有办法去固化已知的 Bug最大程度保留下来我们的测试案例。
这其实才是最核心的一个认知问题:我们应当重视我们的测试代码,它同样也是我们的开发成果,理应获得和模块的功能代码同等重要的地位,理应被保留下来。
解决了这个认知上的共识问题,自动化测试就能够被很好地推动起来。当前这方面的工具链已经非常完善,不至于会在工具上遇到太大的障碍。
## 持续构建,持续发布
其次,我们降低软件工程不确定性的方法是:持续构建,持续发布。
我们鼓励更小的发布。我们鼓励更短的发布周期,更高的发布频率。这能够让发布的负担降低到最低。
这种极度高频交付的机制与传统工程的质量管理机制迥异。但是它被证明是应对软件工程不确定性的最佳方式。为什么会这样?
其一,交付的功能越少,因为错误而发生回滚的代价越低,影响面越小。如果我们同时发布了数十个功能,却因为某一个功能不达标而影响整体交付,这其实是降低了软件的功能交付效率。更好的方式显然是把这个出问题的功能回滚,把其他所有功能都放行。
其二,交付频率越高,我们对交付过程的训练越频繁,过程的熟练度越高,执行效率也越高。当交付成为一个自然习惯后,我们会把交付看作功能开发的一部分,而不是以前大家对研发的理解,认为做完功能就完事,后续上不上线与我无关。我们会鼓励更多把研发的绩效与功能线上的表现关联起来,面向客户价值,而非仅仅面向功能开发。
当然这种极度高频交付的机制,意味着它对软件工程的系统化建设有更高的要求。
当然,除了日构建与发布平台外,我们也需要在其中加入各种质量管理的抓手。比如:
* 自动化运行单元测试案例unit test
* 单元测试覆盖率检查code coverage
* 静态代码质量检查lint
* 人工的代码互审code review
* 灰度发布gray release)
* A/B 测试A/B testing
* ……
## 结语
今天我们更加完整地探讨了软件工程的质量管理。整体来说,软件工程与传统工程在质量管理上的理念是迥异的,甚至往往是反其道而行之的。究其原因,还是因为软件工程的核心在于如何在高度的不确定性中找到确定性。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们谈谈 “开源、云服务与外包管理”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。