Friday, February 5, 2016

三件事开启你的敏捷转型之旅

开启敏捷之旅的三个实践:持续集成(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中关注注释,代码执行细节,盲目听从一些老员工的建议。这样做不仅不能帮助团队成员得到有效的反馈,提高产品质量,也无法促进知识传播,建立团队文化。

因此,要让这个实践真正有效,我们要更多关注以下左项:
  1. 业务需求理解 > 代码执行细节
  2. 代码业务逻辑 > 代码风格规范
  3. 是否测试覆盖 > 人为逻辑判断
  4. 知识传递 > 听从权威
同时,关注别人代码中的亮点,指出来。

团队可以在每天下班前或站会后,半小时左右,团队中每个人通过介绍实现的需求、代码设计、读代码实现,使团队其他成员理解自己的代码,让大家提出反馈。

总结

敏捷转型没有捷径,通过踏踏实实提高产品质量,以较小的改变带来较大的收益,建立起持续反馈和改进的文化,这样才能慢慢提升团队适应变化的能力,使团队习惯变化、拥抱变化,真正开启通向敏捷的大门。




Tuesday, February 2, 2016

Code Review的正确姿态

正确的做Code Review可以帮我们解决两个问题:
  1. 减少Bug
  2. 使修改别人的代码变得容易


那么怎么样才是正确Code Review的姿态呢?


我们先从bug说起


bug的形态可以分为以下几类:
  • 逻辑不全 - 如缺少输入检查、缺少逻辑判断、少存一个字段等
  • 逻辑错误 - 如字段错位、与或搞错了等
  • 业务理解偏差 - 这个容易理解,就不解释了
  • 集成错误 - 如组件与组件之间接口不匹配等

要让别人鉴别出这些问题,首要任务就是让别人通过理解业务需求来理解你在做什么,对应的需求有没有相应测试覆盖,有没有被遗忘的角落或理解上的偏差。其次再将需求对应到功能代码上,讨论你实现的合理性,是否有更好的实现,是否有已存在的技术,可以简化现在的设计等。
因此这里我们得到了三个关注点:
  • 业务需求的理解
  • 相应的测试覆盖
  • 可读性高的代码

然后我们说说代码共享


容易被修改的代码通常具有以下几个特征:
  • 意图明显 - 让阅读者很容易理解作者为什么要写它
  • 容易阅读 - 有良好的层次,业务逻辑清晰
  • 反应当前业务 - 可以通过阅读代码了解系统现状,从而很容易找到修改点
所以这里要关注的依然是:可读性高的代码,也就是说同样的功能,能越快读懂的代码越好。关于更可读的代码,请参考《Clean Code》。

Code Review在这里能起到避免误读的问题,所以我们还是应该像上面提到的一样,先理解需求,随后看一下代码和需求是否一致。

另外,我们经常看到一些团队,在做Code Review的时候会有一些低价值的讨论,比如:
  • 用if还是switch
  • 注释应该怎么写
  • 大括号前要不要换行
  • 代码在什么情况下会被执行
  • 把Tech Lead的话当圣旨
虽然以上讨论能形成一些代码规范,但这些规范只能非常有限的帮助减少bug和代码共享。

因此,我们从要解决的问题中,找到了Code Review的正确姿态,总结如下:
  1. 业务需求理解 > 代码执行细节
  2. 代码业务逻辑 > 代码风格规范
  3. 是否测试覆盖 > 人为逻辑判断
  4. 知识传递 > 听从权威
同时,关注别人代码中的亮点,指出来。

那么下一个问题,Code Review的频率应该是怎么样的?


通常,大家都希望反馈的周期越短越好,只有得到足够快速的反馈,我们才能尽早的避免引入问题,保持始终往正确的方向前进。
所以基于期望得到最快速的反馈,最好的实践是结对编程。
但是结对编程对于刚刚开始敏捷转型的团队来说,实践难度太大,团队很难接受这种思想,也很难放弃现有习惯去工作。所以比较折中的方式就是进行每日Code Review,也就是团队每天找一个时间,大约半小时左右,在一起互相Review代码。这个时间可以在下班前,也可以在每日站会后,由团队根据自身情况而定。


总结


总之,通过每天在Code Review中关注业务、自动化测试和代码可读性,每个成员分享和传递知识,从而增进团队成员之间的相互了解,共同实现更高质量的产品,进而帮助团队在走向敏捷的过程中迈出重要的一步。 

Monday, February 1, 2016

自动化测试简介

为什么要做自动化测试?

自动化测试可以做到人类无法做到的快速验证和反馈,从而提高软件质量和降低修复成本。
想象一下如果不写脚本去做压力测试,会是什么情形?

测试总类繁多,包括但不限于:

通过几十年的技术积累,上面所诉的90%都有相应的技术做自动化支持。由此可见业界对于测试自动化的推崇。


根据测试金字塔原理:


图:测试金字塔

金字塔越下层,说明运行速度越快,编写成本越低,应该在所有测试中占比最多。
金字塔越上层,说明运行速度越慢,编写成本越高,应该在所有测试中占比最少。

由此引出保障软件产品质量的三个基本测试:单元测试、集成测试和功能测试。

底层单元测试:

我们推荐的单元测试编写方法是TDD。通过TDD帮助我们理清需求,想清设计,从而编写更高质量的代码。你得到高覆盖率的单元测试仅仅是额外的奖励。
Java中我们用到的测试框架通常是JunitMockito
Scala我们会用Specs2

测试的编写应该从需求出发,每个测试都表达一个功能点,而不是从代码的角度去讨论单元测试,这点同时应该在方法名中体现。比如在猜数字的练习中,我们的测试描述应该写成 "should_tell_how_much_numbers_are_matched_when_I_guess",而不是"test_method_guess"。

另外为什么先写测试?
试着回答两个问题:先有需求还是先有代码?如何验证代码的正确性?
答案显而易见:先有需求,“实现了的需求”是检验代码正确性的唯一标准。
因此,我们应该根据需求编写相应的测试,并通过所有测试,以此来证明我们写的代码是可靠的。

具体写法,请参考示例

中间层集成测试:

在Restful应用中,集成测试通常是测试服务API。这种测试可以对某个服务进行端到端的测试,同时又避免了处理UI的复杂性。
API的测试,基于Java最常用的就是RestAssured,再加上Spring Test内存数据库等技术,使API测试能独立、可重复的运行。


上层功能测试:

功能测试也就是应用测试,是对产品整体的验证。功能测试只关心用户能接触到的内容,模拟用户使用产品,验证产品是否能解决用户问题,符合场景预期等。
最常见的就是UI测试,通过Selenium来模拟实际页面操作来验证产品功能。



所有测试都应遵照以下流程:

  1. 准备测试环境和被测对象
  2. 给定输入
  3. 验证输出

所有测试都应该符合以下标准:

  • 独立性:测试应能够清楚的表明一个功能或场景
  • 可重复性:测试应可重复运行,且都以同样的形式成功或失败
  • 自我验证:测试要无歧义的表达成功或失败
  • 完整性:测试应该不需要人为参与做类似于调整数据的操作

最后,要学会如何写自动化测试,重点是多练。可以从基础的题目开始练起,用自己擅长的语言。重点训练自己的测试思维,即我们是在验证用户价值,这样才能使测试真正有效。


Resources:
Kata练习题:http://codingdojo.org/

Books:
程序员的职业素养:http://book.douban.com/subject/11614538/