开启敏捷之旅的三个实践:持续集成(Continuous Integration, 以下简称CI),自动化测试,每日Code Review
为什么是这三件事?
抗拒变化,效果不佳,扎根不实,缺乏学习氛围等因素,往往是传统团队在敏捷转型中失败的重要的原因。
比如有些团队采用Scrum,引入迭代,计划会议,站会,回顾等实践,尝试两周上一次线,结果上线后产品质量很差,引来各方投诉,同时下个迭代有一半的时间在修bug,严重影响了产品进度,开发开始叫没时间,产品经理吹促着快点完成功能,于是,各种实践开始延期或取消,项目经理决定延长迭代周期,慢慢地团队回到了几个月上一次线的老路。
还有些团队,站会变成团队成员的汇报会,引入了BA却缺乏与其他成员的沟通,团队成员都不愿意暴露弱点,抗拒新知识等。这些团队的管理者,高估了成员走出舒适区和适应变化的能力,很容易导致团队转型的失败。
团队要能成功转型敏捷,必须要一步一步走,一步一个脚印,让团队每个人都能看到进步,感受到好处,并且能让这种获得的进步能在团队中长期保存,形成文化,让新来的人也能继承这些做事方式。
图:团队能成功变革的三要素
我们来看这三个实践是如何相互配合,做到以小的变化,得到大的效果,最终在团队中形成文化的。
- CI可以在任何形式的团队中引入,帮助团队成员减少手工重复劳动,提高效率,比如打包和部署,回归测试等。同时CI不需要改变团队现有开发流程,对工作习惯的改变较小,且易于理解,容易被团队接受。
- 自动化测试配合CI一起实施,能尽早的发现问题,尽早的修复问题,有效的提升产品质量。高质量不仅能帮助产品成功,还能减少团队的计划外工作,给团队带来更好的预估。
- 每日Code Review,通过增加团队面对面交流,使知识得以传播,在团队中建立文化。虽然我们认为结对编程是更好的实践,可以更快速的传播知识,但对于刚刚开始敏捷转型的团队来说,实践难度太大,团队很难接受这种思想,也很难放弃现有习惯去工作,所以每日Code Review就是一种较好的折中。每天的相互反馈,可以帮团队尽早发现和修复问题,使每个人都能向相同的方向前进,同时可以有效传播好的工程实践文化,使新人快速了解团队的做事方式,帮助新人融入团队。
综上所诉,团队能以最小的变化引入持续集成,减少浪费,加上自动化测试使之更有效,再以每日Code Review在团队中建立文化,从而为成功敏捷转型打下基础。
应该怎么做?
CI
CI是一种软件开发实践。在持续集成中,团队成员频繁集成他们的工作成果,一般每人每天至少集成一次。每次集成会经过自动构建的验证,以尽快发现和修复集成错误。
在实施CI的过程中,要有独立的CI服务器,并在服务器上自动化以下事项:
- 持续检查:静态代码扫描,关注代码质量
- 持续打包:自动编译和打包,为部署做准备
- 持续部署:能使同一个包,对各环境做自动或一键部署
- 持续验证:自动运行所有测试脚本,验证产品功能
- 持续基础设施:自动创建所需要的环境,并且将代码纳入源代码版本管理
- 持续报告:建立CI状态墙,所有人实时关注;展示和发送每次运行的结果报告给相关人员
每完成一项自动化工作,都意味着团度效率的提升。
另外,所有人都要参与:
- 开发人员
- 提升提交代码的频率,每天至少一次
- 编写单元测试和集成测试
- 在代码提交前通过所有的本地测试
- 第一时间修复CI上失败的Build
- 测试人员
- 尽早参与到整个产品的交付流程中,站在用户角度理解需求
- 自己或和开发一起编写自动化验收测试
- 运维人员
- 搭建CI服务器和CI状态墙,优化CI速度和稳定性
- 帮助产品团队建立CI流程
- 做好环境和数据库的版本管理
- 管理人员
- 做好各种支持,如激励员工,提供资金购买服务器,大电视机等
工具:Go CD, Jenkins, Bamboo, travis-ci, TeamCity ...
自动化测试
根据测试金字塔原则,为不同的测试目的写不同的自动化测试脚本。
图:测试金字塔
单元测试,最容易编写,同时可以实现高覆盖:
推荐的单元测试编写方法是TDD。通过TDD帮助我们理清需求,想清设计,从而编写更高质量的代码。
测试因该关注需求,每个测试都应该通过“我要实现什么业务”的方式思考。我见过很多人在讨论单元测试的时候,都纠结于是给所有公共方法写单元测试,还是只给部分写,是覆盖所有分支,还是覆盖重要分支。而事实上这种讨论没有什么意义,测试要保障的是价值的实现,只有关注需求而不是关注代码,才能写出有价值的测试。
单元测试应该隔离依赖,可以反复且快速运行,并且只有一个失败的原因,一旦失败能快速定位问题,帮助开发人员快速修复。
中间层集成测试,编写难度适中,实现组件级重要场景覆盖:
在产品开发中,无论是传统的横向根据技术分层,还是现在的纵向根据业务分块,都免不了出现大量组件,以及组件之间的互相依赖。比如持久层要访问数据库,资源组件要访问网络等等。
集成测试就是把一个或多个组件,当成一个整体来进行独立的功能测试。这种测试通常不包含前端UI界面,这样可以避免处理UI所带来的复杂性。
集成测试有时需要通过一些手段,自建依赖,比如自己创建像H2这样的内存数据库,或者用mountebank之类的工具自建API服务,以便能在独立的环境中重复运行。
上层功能测试,最难编写,重点覆盖完整的业务流程:
功能测试也就是应用测试,是对产品整体的验证。功能测试只关心用户能接触到的内容,模拟用户使用产品,验证产品是否能解决用户问题,符合场景预期等。
一般功能测试会通过Selenium等自动化框架,来模拟实际用户对UI界面的操作,再通过验证界面上返回的信息来验证功能。
功能测试往往会有比较长的操作步骤,因此运行一个测试也会有比较长的耗时。同时功能测试会涉及多个页面和组件,所以运行失败后的问题定位往往会比较困难。这就要求一旦找到问题后,必须要补单元测试来对问题进行覆盖。
总之,根据测试金字塔,为不同目的编写不同单元测试。对于传统团队或遗留系统,可以从单元测试和功能测试入手,让开发写单元测试,测试人员写功能测试,在一段时间之后再引入集成测试。
每日Code Review
在团队转型初期,我见过一些团队会在Code Review中关注注释,代码执行细节,盲目听从一些老员工的建议。这样做不仅不能帮助团队成员得到有效的反馈,提高产品质量,也无法促进知识传播,建立团队文化。
因此,要让这个实践真正有效,我们要更多关注以下左项:
- 业务需求理解 > 代码执行细节
- 代码业务逻辑 > 代码风格规范
- 是否测试覆盖 > 人为逻辑判断
- 知识传递 > 听从权威
同时,关注别人代码中的亮点,指出来。
团队可以在每天下班前或站会后,半小时左右,团队中每个人通过介绍实现的需求、代码设计、读代码实现,使团队其他成员理解自己的代码,让大家提出反馈。
总结
敏捷转型没有捷径,通过踏踏实实提高产品质量,以较小的改变带来较大的收益,建立起持续反馈和改进的文化,这样才能慢慢提升团队适应变化的能力,使团队习惯变化、拥抱变化,真正开启通向敏捷的大门。