本文作者:PingCAP 唐刘
在 关于产品质量的思考 - 我的基本认知 这篇文章里面,我画了一个因果循环图,强调了一个好质量的产品对于客户增长的重要性。那么如果保证质量好,很大的一个工作就是测试。不过这篇文章并不会讲我们如何来测试 TiDB 的,如果想了解更多我们如何测试分布式系统的,可以看我们的 博客 以及贵司 CEO 的文章 分布式系统测试。
这篇文章,我想借用下《创新者的窘境》这本书的标题,想聊聊,我们在开发和测试之间做的一些取舍,面对不少困难情况下面做出来的一些选择。
在开始之前,仍然有如下的几个声明:
- 我说的不一定是对的。我也会定期刷新我自己的认知。
- 这仅仅只是我自己关于质量的思考,是我自己在 PingCAP 的经验总结,也并不一定适用于其他公司。
代码覆盖率,多少才合理?
在聊代码覆盖率之前,我们先探讨几个大家非常关心,不过却没有标准答案的问题。
『如果代码覆盖率很高,甚至到了 100%,产品质量好不好?』,关于这个问题,我相信没人能承诺产品质量一定没问题,不过大概率还是会相信产品的质量还是不错的。
『如果代码覆盖率很低,譬如低于 50%,甚至更低,产品质量好不好?』,我相信大部分人还是会认为质量不会好。
『代码覆盖率多少是合理的?』根据我的经验,大部分人会认为 75-85% 左右,坦白的说,这个并没有啥标准,各个公司自己定。
『如果代码覆盖率低于公司定义的标准,譬如 80%,我们要做什么?』
这个问题如果放到几年前问我,我大概率会不假思索的回答,『这还不简单,强制让研发把测试覆盖率提上来呗。』,我相信,这也是不少人的答案。
不过真的是这样吗?我现在是愈发的怀疑。当前对于 TiDB 这个项目,代码覆盖率(2024-03-22 UTC-7 这个点)是 71%,不过我并没有强制让研发快速地将测试覆盖率提升到 80% 甚至更高,主要有几点吧:
- 要提升代码覆盖率,还有更前置的事情要做好,这就是单元测试的改进。如果研发都不能很好的写单元测试,谈代码覆盖率就没太多的意义。这里,也可以通过写功能测试,集成测试来提升代码覆盖率,不过这里就需要做更多的工作了,我更希望先把基础打好,在推进下面的事情。
- 研发还有不少更高优先级的事情要处理。在之前的文章里面我也写到,为了提升产品的竞争力,我们会开发 feature,虽然我们在刻意的控制 feature 的个数,不过还是有不少。另外就是历史遗留的技术债务也不少,这些都是现阶段我们能看到的高优先要交付给客户以及处理的问题。
- 哪一些模块需要快速地提升覆盖率,哪一些模块可以先不用处理,我们的判断标准是怎样的?毕竟我们资源有限,先从哪一个功能,哪一个模块开始,才能保证收益最大,这是必须要考虑的。
- 代码覆盖率从 71% 提升到 80% 意味着什么?这个是一个很难回答的问题,意味着我们的质量一定能变得更好吗?这个我没法回答。当前,如果一些问题我自己还没有答案,或者团队没给我答案,那么对于推行一些事情,尤其是涉及到全员的事情,我都会持保留态度。
所以,我并没有强制给大家安排提升测试覆盖率的工作(不过我给某些团队强制安排了重构 UT 和添加测试的工作,也会间接的改善测试覆盖率),但并不代表着我不重视测试覆盖率,这个毕竟也算是一个能反应产品质量的指标。所以,当前的一个要求就是至少保证测试覆盖率不要下降,以及新开发的 feature 优先保证测试覆盖率符合标准,至于存量的代码,一点一点改进吧,从长期来看,我们还是要把测试覆盖率慢慢提升上去的。
测试的成本
我一直非常佩服的在测试里面做到极致的一个项目是 SQLite,大家可以看他们的一篇文章,How SQLite Is Tested,里面详细的讲了他们是如何测试 SQLite 的。
里面提到了一个非常关键的数字,SQLite 的 branch coverage 是 100%,这是一个非常令人羡慕的数字。不过对于我来说,羡慕一下就行了,我是不会在 TiDB 里面要求 branch coverage 100% 的,而且我们也做不到。从某一方面来说,我甚至有一个不一定正确的认知,就是不停的追求提升 coverage 是一件边际成本递减的事情,而且还会影响我们在产品 feature 上面的快速迭代。
所以,当前在 PingCAP,单纯的去追求测试用例的个数,以及单纯的去追求引入不同的测试方法,我是不鼓励的(这里绝不是不让大家加测试)。我们还是需要根据我们实际的情况来看,到底我们整个产品质量的最 Top 的缺陷在哪里,刻意的去提升这方面的质量。
另外,我们当前已经积累的测试,我们也需要去看实际这个测试能带来的效果,一个测试如果写的不好,或者运行的方式不对(譬如应该放到每日回归执行的,结果放到了每次代码合并运行),都可能会影响整体研发的效率。不知道大家听说过一个来自 Oracle 的故事没有,You can't change a single line of code in the product without breaking 1000s of existing tests, Oracle 的测试太多了,多到几乎任何的代码改动都会 break 测试,然后研发就去花更多的时间修复这些测试。我非常羡慕 Oracle 这样强大健壮的测试体系,不过另外想想,如果当前在 TiDB,研发任何的改动都会花更多的时间去修复 break 掉的测试,也可能是一件挺恐怖的事情,TiDB 应该还没复杂到这种地步。
另外一个成本就是实实在在的机器成本,跑测试是需要机器的。做为一个创业公司,我们的预算非常有限,所以如果测试的效率不高,譬如跑得太慢才能发现问题,或者多个测试用例其实重复等,都是对于资源的浪费。而测试效率不高另外一个影响就是,当多个研发提交代码,就会出现因为机器资源不够测试大量排队的情况,影响了产品迭代的效率。
所以,就跟代码有时候需要重构,测试用例也是需要定期的重构,变得更加高效能执行,以及更好维护。
写在最后
这篇文章的标题我一直在纠结,也算是一个窘境吧。也是对于最近经历的事情一些有感而发吧。
一方面,我知道代码覆盖率非常重要,不过也知道当前研发工程师的给客户开发功能的进度压力,还有疑难问题的解决的压力等,很多时候只能取舍。
再就是我们的测试用例越来越多,在测试预算有限的情况下,很容易就出现大家争抢资源,还有排队等待的情况。如果不做好测试的分级和管理,单纯的添加测试,或者遇到失败的测试就整个重试跑一遍,这些都会不断地加剧测试资源的争抢和冲突,最终就类似于公地悲剧,所有人都受到影响,大家的效率都被拖慢了。
参考
- 关于产品质量的思考 - 我的基本认知
- 关于产品质量的思考 - 如何评估质量
- 分布式系统测试那些事儿 - 理念
- 分布式系统测试那些事儿 - 错误注入
- 分布式系统测试那些事儿 - 信心的毁灭与重建
- How SQLite Is Tested
- Oracle Database 12.2. It is close to 25 million lines of C code. | Hacker News