谈谈测试与代码质量
平常开发中,你花多少时间写测试?覆盖率有多少?除了单元测试,其它的整合测试以及 UI 测试有实践么?
以我个人经历来说,在国内工作时几乎不写任何测试,仅限于倒腾过测试。即使阿里这样的大公司,业务部门也几乎不写任何测试,基础研发部门或许很有节操,具体情况不得而知不好瞎猜。为什么不写测试,这跟整体的氛围有关,真有人在意你的代码质量吗?没有!大家在意的是业务结果。原来的负责人升职加薪之后项目交给后面的人维护,如果新需求不多,代码还过得去维护着就行。需求实在太多,维护不下去了,那只能重写了(自动化测试都没有,谈不上重构)。
国内开发节奏太快,着急于抢占市场,不断试错,计划赶不上变化,这周加的功能下周都可能就要废掉。这种情况下,写测试完全得不偿失,自然也就不会花时间在写测试上了。那如何保证代码质量呢?一般在上线截止日期前,集中时间手动测试,或许有些公司将这些任务外包给其它公司来做,毕竟大多数的开发可不屑于干这么枯燥无聊的活。
现在就职的公司在测试上做的可谓非常专业。我们的 QA 团队会支持各个业务团队,QA 是质量辅助而非质量保证。这儿有个文章介绍质量保证与质量辅助。QA 的职责更多的是监督,指导我们来完成测试任务,而非自己测试。他们会收集各种数据,建立指标来评估代码质量。另外,也会帮助新团队或新项目建立自动化测试,指导新人完成测试工作。
再说说我的发布模式,我们的发布周期是每日发布,也就是说当你的代码合并到 master 时必须保证它是正确的。因为第二天固定时间就会部署到线上,部署之后自动化测试通过就认为部署成功。除了单元测试,我们还需要用 UI 测试覆盖到所有的关键路径,也即冒烟测试。冒烟测试只会测试整体流程,允许存在 bug 但不是严重的,不影响正常的流程即可。
一个潜在的 bug 发现的越早所需的修复成本越低。单元测试并不能保证万无一失,模块与模块之间功能是否匹配还需要整合测试来保证。现在的问题是我们为什么而测试,于是,在开发前我们列出所有的需求点来,以保证我们会用适当的测试来覆盖这些需求点。
最后,在后端和前端之间,我们加入了合约测试以保证后端的 API 变动不会影响到前端代码。
现在的趋势是,开发将承担运维和测试的工作,而运维和测试只做些支撑性的工作。某些大厂强制测试转开发,就我之前见到后端写前端的经验来看,除非是内驱的并且公司给予足够的时间来转,否则只是添乱。口号喊的很好,全栈工程师,简单易上手,但是只要不让我接手维护他们的代码随便他们怎么折腾都行。
说说发布周期,每日发布就必须要求开发对代码有信心任何时候合并进去就要能够部署,避免开发将测试责任推卸。缺点是用户不能很好的利用缓存,用户重新加载新资源却并没有任何新的可用功能。将任务切分很细只会带来更多的工作量,而开发分支上本来就不是直接可交付给用户的。个人来看,除非必要的 bug 修复,完全可以等到 Sprint 结束一起发布。
关于测试,我并不是认为测试写多了,代码质量就会变好。测试只应该是辅助,不应该为测试而改动代码的可读性或使得代码变得复杂。真有一个极端的同事,几乎所有的代码都是依赖注入,一个功能函数的参数是一个接口。比如,fetchUser()
依赖的 request
只是一个接口,你需要一层一层往上查看,才知道这个是如何实现的。
我知道这会让我的代码非常容易测试,但毫无疑问它让代码变得更复杂了。如果说好的架构师懂得如何权衡各种技术做出取舍,好的程序员一样也需要懂得取舍。测试的增加必然会拖累产品迭代的速度,有时我常会想,我现在增加的一个 UI 测试,真的以后这儿会做改动吗?或许那时交互的需求早就变了。我同样好奇这些测试到底帮助我们揪出了多少个潜在的重大 bug,是否值得我们的投入。
关于是否应该面向需求点检查覆盖率,我们也有不同的意见。个人不太认同,因为我们做单元测试很多时候是知道这个模块会被复用,其它人在使用这个模块时,如果需要改动,单元测试可以很好的保证其不被破坏。这个可复用的模块即使已经被整合测试覆盖到,也应该有对应的单元测试。整合测试并不能够帮助我们迅速定位问题,只是确保某一个功能是否完好。
对于测试,总的来说,它带来好处,但是也不是免费的。对可复用的模块,基本的单元测试是必需的,写单元测试也可以帮助你发现代码异味。关键路径的冒烟测试也是有必要的,至少确实会减少人工成本。业务逻辑代码因情况而定,非关键功能且你认为是一次开发即可的,你老板也不会让你投入这么多时间写测试的。如果你没有写过测试,开始尝试用单元测试覆盖你的可复用模块,尝试写些 E2E 测试减少重复劳动吧(E2E 测试远并没有你所想的复杂)。