LinkedIn:移动APP开发中的自动化测试与持续部署流水线

最近,有很多人问我关于“移动应用开发中的自动化测试如何做?持续集成和部署流水线从哪里开始?”的问题,觉得有必要写一写。恰好这个周末在家无意间看到Linkedin的做法,觉得非常有意思,尤其是截屏做对比测试的做法,在移动应用中比较少见, 在这里与大家分享。原文地址在这里

Continue reading LinkedIn:移动APP开发中的自动化测试与持续部署流水线

围绕最终交付物,而不是角色来组织软件交付活动

——持续交付与跨功能团队

在实施持续交付的过程中,我们很容易聚焦于自动化和工具,因为作为起点,它们通常是最容易做的。然而,持续交付的成功实现,还依赖于根据最终交付物而对组织结构所做的优化。对于持续交付来说,最大的障碍是依据角色和分层结构来组织团队,而非业务上的最终交付物(即产品或服务)。

Continue reading 围绕最终交付物,而不是角色来组织软件交付活动

持续集成案例分析系列(1)——大规模项目团队持续集成历程

这篇文章是我在两年前写的,记录了一个150+人的软件团队(最多时近200人)如何在一个庞大的遗留系统上,通过逐步建立一个持续交付部署流水线,从而达到频繁发布的状态。最终在该团队的持续交付基础设施中,共有260台服务器用于构建、测试和部署(几乎全部是虚拟机)。而这个产品也可以每六周发布一次

Continue reading 持续集成案例分析系列(1)——大规模项目团队持续集成历程

《持续交付》封面上的故事——福斯铁路桥

不知道有多少人把《持续交付》一书的前言看全了。最后一部分讲的是福斯铁路桥(本站Logo上的这座美丽的桥),并把它和软件做类比。现摘录如下:

福斯铁路桥是英国第一座使用钢铁建造的大桥。其钢铁使用最新的西门子马丁平炉工艺制造,并由在苏格兰的两个钢铁厂和威尔士的一个钢铁厂交付。钢铁是以管状桁架的形式运送的,这是英国首次使用大规模生产的零部件组装桥梁。与早期的桥梁不同,设计师John Fowler爵士,Benjamin Baker爵士和Allan Stewart计算了建筑压发生率,以便减少后续的维护成本,并计算了风压和温度对结构的影响,而这很像软件开发中的功能需求和非功能需求。他们还监督了桥梁建设,以确保这些要求都能得到满足。

当时有4600名工人参与建造该桥,其中不幸死亡约一百人,致残数百人。然而它仍是工业革命的一个奇迹,因为1890年建成时,它是世界上最长的桥,而到了21世纪初,它仍是世界第二长的悬臂大桥。就像长生命周期的软件项目一样,这座桥需要持续维护。这已在设计时考虑到了,大桥配套工程中不但有一个维护车间和场地,而且在Dalmeny车站还有一个50个房间的铁路“聚居点”。据估计,该桥的使用寿命还有一百多年。

《持续交付》中文版已上架销售,欢迎对译文进行意见反馈。

持续交付, continuous delivery《持续交付》中文版已于2011年10月17日上架销售。

该书由Jez Humble 和 Dave Farley历时三年完成,

Martin Fowler为该书作序,并称其为“2010年最重要的持续书籍”,

同时,在2011年8月获得 “Jolt 杰出奖”。

从下面网站均可购得:

如果您在书中发现有些地方翻译欠妥,您有多种渠道反馈建议。

  1. 请在下面直接回复即可。内容包括:问题所在页号、原文句子,以及您的建议。
  2. 点击这里,进入图灵社区的《持续交付》专栏勘误。
  3. 直接邮件给译者,邮箱是:qiaoliang.email@gmail.com

谢谢!

 

持续交付成熟度模型更新,新版本v1.2发布

《持续交付》一书中提供的“持续交付成熟度模型”是1.0版本。

这是经过再次调整的改进版,更具有指导性和可操作性。

使用说明:

建议使用该模型进行现状分析,发现改进点,不建议将其作为绩效衡量的标准。

一、七个维度

它们分别是:

1. 持续集成(Continuous Integration)

2. 环境与部署(Environments and Deployments)

3. 可视化与可追踪性(Visibility and Traceability)

4. 测试(Testing)

5. 数据管理(Data Management)

6. 配置管理(Configuration Management)

7. 组织协调性(Organisational Alignment)

二、每个维度又分成五个级别,它们分别是:

一级:阻碍级(Regressive)

二级:可重复级(Repeatable)

三级:可定义级

四级:可定量级(Quantitatively managed)

五级:优化级(Optimizing)

其中,持续集成维度的五个级别分别是:

一级:阻碍级(Regressive)

1. 软件的构建过程是手工的。
2. 构建过程冗长,而且其中的主要步骤常常出错。

二级:可重复级(Repeatable)

1. 在开发人员的代码上进行定期的自动化构建和单元测试。
2. 利用自动化过程,能够从源控制中重新生成任意一个构建版本。
3. 开发人员的提交频率是不定的。

三级:已定义级

1. 每次提交都会触发构建和各类测试。
2. 公共工具集中的脚本或工件得到重用。

四级:可定量级(Quantitatively managed)

1. 构建数据度量项被收集,高度可视化,并执行相应的改进活动。
2. 构建失败不会没人管。所有团队成员至少每天提交一次。
3. 尽可能在最后时刻(即将发布时)才拉发布分支。

五级:优化级(Optimizing)

1. 随时可以从主干上拿到已全面集成且生产环境可部署的构建版本。
2. 关注点是:随着对代码质量信心不断提交,能够进行更加频繁的提交。

环境与部署的五个级别分别是:

一级:阻碍级(Regressive)

1. 手工准备环境,无适当方法管理环境冲突。
2. 手工部署软件。

二级:可重复级(Repeatable)

1. 环境已被定义,并可自动化地准备和控制。
2. 部署操作是手工和自动化相结合才能完成。

三级:可定义级

1. 开发和测试环境是全面自动化且自服务的。
2. 已具备 “点击按钮即可向任意环境进行部署”的能力。
3. 为了完成自己的工作,每个人都有相应权限访问并操作相应的环境。

四级:可定量级(Quantitatively managed)

1. 协调的部署管理。
2. 发布计划自动产生。
3. 对所有的失败进行根因分析。
4. 回滚流程被脚本化,并被管理起来。
5. 建立了环境和系统健康状态指示板(Dashboard),其上显示的数据被监控和报告。

五级:优化级(Optimizing)

1. 环境的准备是全自动化的。
2. 已具备根据需求快速重建完整环境和基础设施的能力。
3. 端到端的业务度量项被监控。

这里可以下载全部

关于DVCS、持续集成和特性分支

本文来自于持续集成英文站的文章《On DVCS, continuous integration, and feature branches》。

为了吸引大家的注意力,我想说:“特性分支是邪恶的化身”。

自2008年起,Mercurial (最近是Git)就成了我日常工作的工具,而且我喜欢使用分布式版本控制系统。正如《持续交付》一书中讨论的那样(英文版第393页和394页),有很多理由说明,与之前已存在的同类工具相比,DVCS代表了一种巨大的转变。但正如所有强大的工具一样,你会有很多种方法来使用它们,但并不是所有的方法都是好的。这里所有的讨论不是想说DVCS不好:使用特性分支和使用DVCS完全是正交的。而且,我认为,DVCS的支持者用这种工具的功能分支来推销DVCS,是对DVCS的一种伤害。

首先看几个定义。请注意,一些人以不同的方式使用这些术语,所以你需要暂时从你的大脑中把先把其它的定义擦去,否则我的讨论就没有什么意义了。

持续集成 是一种实践,用于确保你的软件一直是可以工作的,并且在几分钟内你就能够得到关于 “你的修改是否破坏了系统”的反馈。
特性分支是一种实践, 使用它的人直到他所开发的特性“完成”后才合并回主干(这里的“完成“不是“done done1”原则中的那个“完成”)。
主干是指开发主线—— 通常是指版本控制库中的。你的某交构建就是从这个主干中的一个版本创建的,并会被放到你的部署流水线中。 注意,这些定义对DVCS和其它开源项目,甚至是GitHub,也完全适用。

先让我们解决一个象稻草人一样的争论。当使用版本控制工具时,你就使用一个分支来工作:即本地工作目录。使用DVCS,只是多了一个层次而已,因为你的本地库就是一个有效分支。我并不反对创建分支。我所不赞同的是:让你最终要发布的代码在分支上堆积起来。
下面是我所看到的状况。当你将最张要发布的代码大量地堆积在分支上,会有几个糟糕的事情发生:

  • 这种状态持续时间越长,就越难合并。因为随着其他人向主干提交,主干的代码与你的分支上的代码之间的差异会越来越大。强大的合并工具的确会在一定程度上帮助你,但是任何一个做过很多开发工作的人都会有过这样的经历,即:代码的确成功合并了,但是应用程序运行不起来。随着需要合并的那些代码数量的增加,以及初始分支与最终合并时间的增长,发生这种事情的可能性是递增的,而且不仅仅是线性递增。
  • 在分支上的工作越多,在合并到主干时越有可能破坏系统。每个人都有这样的经历:有个问题出现了,你似乎找到了一个非常聪明的解决方案。可是,几个小时(或者几天)后,你才发现需要废弃所有的东西,或者(更常见地)那些引起未预期后果的代码提交。
  • 当多个开发人员同时在这个代码库上工作,并使用特性分支开发时,使代码重构变得很难。如果我重构并且提交到主干了,而其他人在其分支上已经修改了很多东西的话,当他们向主干合并时就更难了。这就会驱使我不做重构。而不充分的重构就等于劣质代码。

当大家频繁将其代码合并到主干时,就没有这些问题了。如果不这么做的话,随着团队人数的增加,这些问题的痛苦会成指数级增加。而且,还有一个恶性循环: 这种痛苦的自然反应是:更少做合并。正如我喜欢说的,当某些事情令你很痛苦时,解决方案是更频繁地做,并将痛苦提前。在这里,解决方案是每个人都更频繁地合并到主干。

然而,如果你正在做的一个功能中需要做很多工作,或者你对系统进行大面积修改时,这么做就比较困难了。下面是一些解决方案:

  1. 将Story分解成一些更小块的工作(有时被叫做Task)。我还没有遇到过一大块工作无法分解成更小块的工作——通常少于1小时,并且肯定用不了一天时间——这样让我即能达到目标,又能保证系统可工作且可发布。这需要细心的分析、讨论、思考和严格的纪律。当我无法找到在增量开发方式下几小时内可以完成的工作时,我就会先对某些想法做一些试验(敏捷中叫做Spike2)。至关重要的点在于:我能尽早地对我的方案进行快速验证:是可以解决问题,还是会对系统造成不良后果,影响了其他人的工作,或者引出了回归缺陷(这也是持续集成的动机所在)。
  2. 在实现Story时,最后再做面向用户那部分接口。先从业务逻辑及以下部分下手。使用TDD完善你的代码。频繁提交,与主干合并。最后再完成界面部分。
  3. 使用抽象分支方法 来对复杂或大范围变更进行增量开发,同时保证系统一直处于可工作状态

你怎么知道什么时候没有合并的东西太多了呢?想像一下,假如你是一个开源项目的维护者,有一个你不认识的刚刚提交了一个补丁。你会马上合并它吗?你能记住它与主干之间的所有diff吗?你能保证在不需要问你的前提下,团队其他人员能在一分钟之内就能清楚地理解这些diff吗?如果你对上述所有问题的回答不是“Yes”, 那么你就要停止工作,将其分成更小的工作单元。
很明显,只要 “特性”足够小,我并不真正反对特性分支。然而,通常使用特性分支的人大多无法通过上一段中的问题测试。真正有经验的开发人员能理解使用特性分支与为了高效使用它而建立的纪律之间的平衡,但仍旧有一定的危险——GitHub就被Forks搞得很乱,这些Forks很多是不可合并的,因为它们与主干的差异太大了。

我更想强调的是下面这个观点,即:让“尽早地持续地交付有价值的软件” 中最重要的实践之一就是确保你的系统一直是可工作的。

对于开发人员来说,达到这一目标最好的方法是:通过确保他们可以将“提交可能对系统造成破坏”的风最小化。我们可以通过“保持小步提交、在主干持续集成,并且有全面的自动化测试套件来验收本次修改的预期行为,且不会引发回归缺陷”,从而达到这一目标。

主干开发,“关上”未完成的功能

软件开发中,“配置开关”根本算不上是一个“新”概念。无论是通过启动时加载配置,还是应用程序运行时动态刷新配置,都可以用来决定或改变应用程序的某种具体行为。比如:

  • 通过配置文件,使用应用程序连接不同的数据库
  • 通过模板配置,达到应用程序“换肤”的目的
  • 通过系统管理员的配置,使不同的用户具有不同的权限

所有这些“开关”都被认为是理所当然的,因为用户的需求就是这样的,“我需要通过xx开关来控制yy”。因此,做为交付团队,也会老老实实地把这些需求实现了。

然而,现在的很多团队,为了能够尽早发布软件,使用主干开发方式,并在程序中加入了另外一种“对用户透明”的开关。这种开关通常有两种使用场景是:

  1. 某些高级(或创新)的特性已经开发完毕,但业务人员根据市场情况,认为不需要投放市场。
  2. 市场需要某些已开发完成的特性,但还有一些特性尚未完成,仍旧处于开发中。

无论哪种情况,都是要求将某些特性进行隐藏后再发布。这种开关常常被认为是不可取的,但现在很多需要“持续交付”的公司都在使用。

(有的同学会说:“这完全可以通过按特性拉分支的方式来解决”。关于这个问题,我会在下一篇中讨论,本次仅限于讨论在使用“主干开发”的情况下,如何正确使用开关。)

开关最容易的地方就是应用程序的用户界面。当为已上线的应用程序重新设计了全新交互界面后,团队无法在一次性在同一发布周期中将其修改完成时,可以 先保留原有页面不变,同时每次仅替换少数的新页面。令所有未开发完成的页面对用户不可见,例如:原有页面的URL是 http://xx.xx.xx.xx/create_user,那么对应的新页面使用http://xx.xx.xx.xx/new_UI /create_user(要对后台逻辑的修改应能够同时响应新旧两个页面的请求)。当新页面完成后,将原来创建用户的页面链接重新指向新页面就可以了。 这样,就能在不破坏原有功能的前提下,做到持续发布。

开关实现形式

另外,对于那些无法在一个发布周期内实现的新功能,我们可以使用下面形式的“开关”:

  • 配置文件,通常在启动时加载

<features>
<feature name=”hq.remote_panel_load” />
<feature name=”el.enable_asset_library” />
…etc…
</features>

  • 在代码中实现

public function isFeatureOn(featureName:String):Boolean {
var nodes:XMLList = xml.features.feature.
(@name==featureName);
return null != nodes && 0 < nodes.length();
}

  • 设计成API

${core-url}/accounts/featureBits?userUid=&orgUid=&

而在编码中,可以使用不同的实现方式:

  • 最简单的检查

if( registry.config.isFeatureOn( featureName ) ) {
// new implementation …
} else {
// the old way …

}

  • 使用策略模式

if( registry.config.isFeatureOn(“ct.analyzer.v2”) ) {
service.analyzer = new Analyzer_v2();
} else {
service.analyzer = new Analyzer();
}

  • 使用工厂模式

public function createMainDisplay():DisplayObject {
if( registry.config.isFeatureOn( “service.panel.v2” ) ) {
return new panel2(); // which extends panel
} else {
return new panel(); // which extends DisplayObject
}
}

  • 使用责任链方式

if( registry.config.isFeatureOn(“hq.trickle_reporting”) ) {
userActionLogger = userActionLogger.setNext(new       TrickleReportNotifier() );
}

注意事项:

  • 提前评估是否需要开关,需要的话提前设计,不应该随意在代码中添加。
  • 定义一个开关命名规则,并在团队中达成共识。
  • 尽可能在设计时考虑在哪些层次上加开关。

“开关”在使用中的反模式:

  • 代码中到处都是开关,个数太多;
  • 在开关完成其使命后,未及时清理,使代码变烂。

现在,很多公司的产品都使用这种开关方式。进一步阅读,请参见:

相关工具:

代码示例来自于:Erik Sowa,”Feature Bits” on slideshare.net

本文版权归作者乔梁所有,转载请包含作者签名和出处,不得用于商业用途,作者将保留“追究法律责任”的权利!

ControlTier,基于命令的自动化部署工具

现在,服务器集群已经是司空见惯的事情了。随便一个小的互联网应用程序都需要用集群来支撑。而当采纳“持续集成”,尤其是“持续交付”实践时,在各种环境上的部署让你发疯。这些环境包括开发环境、测试环境(包括功能测试和非功能测试)、试运行环境和生产环境。而且,每种环境可能会有多套实例,而不只是一套实例。

曾经接触过的一个团队,其持续集成环境中就有260+台服务器,随着开发人员的提交,不同版本的应用程序在这些服务器上一遍又一遍地被部署、测试、卸载。而且,这些服务器的作用各不相同,有的是web服务器,有的是应用服务器,还有数据库服务器,也许还有消息服务器等等。想象一下,如果由你来管理这样一个团队中的服务器集群的部署,你的状态会是什么样子?

其实,当面对这么庞大的集群时,关键问题不仅在于知道每一步怎么做,而是:如何能够在所有的服务器上能够让所有的步骤都能够正确地被执行,而且要按照你期望的顺序,不至于各节点的执行步骤间互相受到影响。

Big problem for cluster management

其实,已经有很多工具解决这类问题了,ControlTier就是其中之一。它是一个跨平台软件系统,用于在多个服务节点中管理应用程序的相关操作。

它既有命令行工具,又有Web管理界面。用户可以自定义命令组,并重用它们。它与Puppet的一个区别在于:ControlTier是基于“对目标进行操作(Activity)”的工具,而Puppet则是基于“定义目标状态”的工具。所以,Puppet的“部署幂等性”在ControlTier里是很难办到的。它们在这一点上的区别也决定了它们使用层次上的不同。在《持续交付》的“环境管理”章节中,说环境配置管理的层次如下图所示:

而它们所服务的位置如下:

当然,你完全可以在系统层使用ControlTier,也完全可以在应用层使用Puppet,但是它们有各自的专长,在其它层次上使用意味着需要的定制工作量稍多一些而已。

你还等什么,赶快使用这些工具,去打造你的完美“持续交付”环境吧!

 

本文版权归作者乔梁所有,转载请包含作者签名和出处,不得用于商业用途,作者将保留“追究法律责任”的权利!