• 图解持续集成纯命令行实现.Net项目每日构建(升级版本)


    图解持续集成
    --纯命令行实现.Net项目每日构建(升级版本)

    该工具源代码下载    

          在《纯命令行实现.Net项目每日构建》实 现了完全自动化、可重复的构建工作,但随着时间的推移,新的问题暴露了:由于所在公司是一家 产品开发+现场实施 型的软件企业,十个人的研发团队并发支持四、五十个项目,每天除了完成客户个性化开发需求和沟通解决现场各种怪异问题之外,还要进行每日构建、版本发布 (我们通常是一周对外发布一次版本,甚至临时版本)、甚至提供不同形式的源代码。在进度比较松的情况还能够勉强应付,但在进度较为紧张的时候,花较大精力 去做这种事情,团队人员叫苦不迭。
         针对这种情况,我们对第一个构建版本进行了改造,加入了主界面,在界面上可以进行不同操作的选择,如下图所示:

    该界面上可以选择不同操作,具体操作如下:
       0、退出:返回到命令行主程序
       1、生成DailyBuild每日版本发布用于测试回归:该选项包括获取源代码、编译及发布,作为BVT和每日测试回归版本,该操作频度一般为日频度;
       2、生成ReleaseBuild发布测试版本:该选项包括获取源代码、编译及发布,为BVT和每周版本发布版本,该操作频度一般为周频度;
       3、打包发布版本,包含帮助文件:打包指定版本程序包,包含帮助文件;
       4、打包发布版本,不包含帮助文件:打包指定版本程序包,不包含帮助文件;
       5、生成ReleaseBuild发布测试版本,并打包含帮助文件:在选项2基础上,打包程序包,并包含帮助文件;
       6、生成ReleaseBuild发布测试版本,并打包不含帮助文件:在选项2基础上,打包程序包,并不包含帮助文件;

    演示说明:
    由于选项1、2较为类似,将以选项1作为例子;选项3、4较为类型,将以选项4为实例;选项5、6较为类似,将以选项5为示例。


    1. 准备工作
    在D:\JRTJ目录下建立如下文件夹:

    • BuildDaily:用于存放构建版本
    • BuildRelease:
    • BuildLogOfAuto:用户存放构建日志
    • 自动编译脚本:存放每日构建脚本及辅助脚本

    自动编译脚本相关脚本说明: 

    • attrib.exe:文件属性修改程序
    • cscript.exe:Win32控制台脚本宿主引擎
    • iisvdir.vbs:IIS6.0中用户创建虚拟目录脚本
    • main.bat:构建主程序
    • replace.exe:运行在Framework1.1下,用于替换关键字的执行程序
    • version.exe:运行在Framework1.1下,用于版本号设置,加入当前时间并且每次自增1
    • version.txt:用于记录当前版本号,具体格式参见构建流程中对版本号说明
    • xcopy.exe:复制文件使用执行程序

    2. 脚本解读
    其中main.bat构建脚本内容如下:

    第一部分 

    第二部分 

    第三部分

    第四部分

    3. 构建执行过程
    3.1. 启动构建程序
    双击或者在命令行中调用main.bat批处理主运行脚本

    3.2. 生成DailyBuild每日版本发布用于测试回归(选项1)
    3.2.1. 步骤一从VSS服务器获取最新版本
    选择操作1回车,显示如下操作

    • 设置日志存放的地址
    • 删除编译文件夹下所有的文件
    • 从VSS获取最新的文件到编译文件夹下

    3.2.2. 步骤二编译程序
    从VSS获取最新版本后,进行如下操作:

    • 遍历设置DLL文件夹中文件为非只读(该步骤该项目个性化需要,不是必须)
    • 版本号的重新设定
    • 程序编译

    3.2.3. 步骤三进行版本发布
    编译通过以后,进行如下操作:
    从编译文件夹复制到发布文件夹
    发布应用
    修改配置文件

    3.2.4. 构建结果查看
    日志信息
    发布程序

    IIS发布程序

    3.3. 生成ReleaseBuild发布测试版本,并打包含(选项5)
    该操作前三步骤与 3.2生成DailyBuild每日版本发布用于测试回归(选项1) 相同,只是在此之上增加了版本打包,如下图所示:

    调用rar压缩文件进行压缩

    在打包路径可以看到打包文件夹PBOC.Web和压缩包PBOC.Web.rar

    3.4. 打包发布版本,不包含帮助文件(选项4)
    通过选项4可以对任意版本继续打包,其中选项3包含帮助文件,4不包含帮助文件。选择选项4可以需要输入打包项目(PBOC.Web)的路径和输入存放打包路径,如下图所示:

    4. 总结
    通过该工具构建好处:
    1、 减少了人力投入到这种重复、无味的工作中,执行构建命令后可以一边喝茶一边等待结果,也可以把宝贵的时间投入到产品开发中;
    2、 降低了项目集成风险,通过每日构建能够在更短的时间内发现代码的冲突,减少了解决这种问题时间。
    存在不足:
    虽然该构建工具中实现了构建完全自动化,但不足的是未引入自动化测试工具,不能够自动获得有效报告和共享构建信息,在后面的文章中,我们将介绍一些工具实现。

    持续集成(第二版)

    --Martin Fowler著 雷镇 译

         持续集成是一种软件开发实践。在持续集成中,团队成员频繁集成他们的工作成果,一般每人每天至少集成一次,也可以多次。每次集成会经过自动构建(包括自动 测试)的检验,以尽快发现集成错误。许多团队发现这种方法可以显著减少集成引起的问题,并可以加快团队合作软件开发的速度。这篇文章简要介绍了持续集成的 技 巧和它最新的应用。
    最后更改于:2006年5月1日


    目录

    • 用持续集成构建特性
    • 持续集成实践
      • 只维护一个源码仓库
      • 自动化 build
      • 让你的build自行测试
      • 每人每天都要向mainline提交代码
      • 每次提交都应在集成计算机上重新构建 mainline
      • 保持快速 build
      • 在模拟生产环境中进行测试
      • 让每个人都能轻易获得最新的可执行文件
      • 每个人都能看到进度
      • 自动化部署
    • 持续集成的益处
    • 引入持续集成
    • 最后的思考
    • 延伸阅读


      相关文章
      持续集成(第一版)
      进化式数据库设计     

     

         我还可以生动记起第一次看到大型软件工程的情景。我当时在一家大型英国电子公司的QA部 门实习。我的经理带我熟悉公司环境,我们进到一间巨大的,充满了压抑感和格子间的的仓库。我被告知这个项目已经开发了好几年,现在正在集成阶段,并已经集 成了好几个月。我的向导还告诉我没人知道集成要多久才能结束。从此我学到了软件开发的一个惯例:集成是一个很耗时并难以预测的过程。但是事实并非总是如 此,我的 ThoughWorks 同事所做的项目,以及很多其它遍布世界各地的软件项目,都不会把集成当回事。任何一个开发者本地的代码和项目共享基准代码的差别仅仅只有几小时的工作而 已,而且这只要几分钟的时间就可以被集成回去。任何集成错误都可以很快被发现,并被快速修复。这鲜明的差别并非源于昂贵和复杂的工具。其中的精华蕴含于一 个简单的实践:使用统一的代码仓库并频繁集成(通常每天一次)。
          当我向别人介绍持续集成方法时,人们通常会有两种反应:“这(在我们这儿)不管用”和“做了也不可能有什么不同”。但如果他们真的试过了,就会发现持续集 成其实比听起来要简单,并且 能给开发过程带来巨大的改变。因此第三种常见的反应是:“我们就是这么做的,做开发怎可能不用它呢?”
           “持续集成”一词来源于极限编程(Extreme Programming),作为它的12个实践之一出现。当我开始在 ThoughWorks 开始顾问职业生涯时,我鼓励我所参与的项目使用这种技巧。Matthew Foemmel 将我抽象的指导思想转化为具体的行动。我们看到了项目从少而繁杂的集成进步到我所描述的不当回事。Metthew和我将我们的经验写进了这篇论文的第一版 里。这篇论文后来成了我网站里最受欢迎的文章之一。
          相关文章:持续集成(第一版)
          这是我的个人网站上关于持续集成最早的文章。尽管我觉得新的更好,但大多数链接还是指向原先的这篇文章。文中我描述了 Matt 于2000年早期在 ThoughtWorks 项目中帮助实施持续集成的经验。
          尽管持续集成不需要什么特别的工具,我们发现使用一个持续集成服务器软件还是很有效果的。最出名的持续集成服务器软件是 CruiseControl,这是一个开源工具,最早由 ThoughWorks 的几个人开发,现在由社区维护。之后还有许多其他持续集成服务器软件出现,有些是开源的,有些则是商业软件,比如 ThoughtWorks Studio 的 Cruise。
    1. 用持续集成构建特性
          对我来说,解释持续集成最简单的方法就是用一个简单的例子来示范开发一个小 feature。现在假设我要完成一个软件的一部分功能,具体任务是什么并不重要,我们先假设这个 feature 很小,只用几个小时就可以完成。(我们稍后会研究更大的任务的情况。)
          一开始,我将已集成的源代码复制一份到本地计算机。这可以通过从源码管理系统的 mainline 上 check out 一份源代码做到。
          如果你用过任何源代码管理系统,理解上面的文字应该不成问题。但如果你没用过,可能会有读天书的感觉。所以我们先快速解释一个这些概念。源代码管理系统将 项目的所有源代码都保存在一个“仓库(repository)”中。系统的当前状态通常被称为“mainline”。开发者随时都可以把mainline 复制一份到他们自己的计算机,这个过程被称为“check out”。开发者计算机上的拷贝被称为“工作拷贝(working copy)”。(绝大部分情况下,你最终都会把工作拷贝的内容提交到mainline上去,所以两者实际上应该差不多。)
          现在我拿到了工作拷贝,接下来需要做一些事情来完成任务。这包括修改产品代码和添加修改自动化测试。在持续集成中,软件应该包含完善的可自动运行的测试,我称之为自测试代码。这一般需要用到某一个流行的 XUnit 测试框架。
          一旦完成了修改,我就会在自己的计算机上启动一个自动化 build。这会将我的工作拷贝中的源代码编译并链接成为一个可执行文件,并在之上运行自动化测试。只有当所有的 build 和测试都完成并没有任何错误时,这个 build 过程才可以认为是成功的。
          当我 build 成功后,我就可以考虑将改动提交到源码仓库。但麻烦的情况在于别人可能已经在我之前修改过 mainline。这时我需要首先把别人的修改更新到我的工作拷贝中,再重新做 build。如果别人的代码和我的有冲突,就会在编译或测试的过程中引起错误。我有责任改正这些问题,并重复这一过程,直到我的工作拷贝能通过 build 并和 mainline 的代码同步。
          一旦我本地的代码能通过 build,并和 mainline 同步,我就可以把我的修改提交到源码仓库。
    然 而,提交完代码不表示就完事大吉了。我们还要做一遍集成 build,这次在集成计算机上并要基于 mainline 的代码。只有这次 build 成功了,我的修改才算告一段落。因为总有可能我忘了什么东西在自己的机器上而没有更新到源码仓库。只有我提交的改动被成功的集成了,我的工作才能结束。这 可以由我手工运行,也可以由 Cruise 自动运行。
    如果两个开 发者的修改存在冲突,这通常会被第二个人提交代码前本地做 build 时发现。即使这时侥幸过关,接下来的集成 build 也会失败掉。不管怎样,错误都会被很快检测出来。此时首要的任务就是改正错误并让 build 恢复正常。在持续集成环境里,你必须尽可能快地修复每一个集成 build。好的团队应该每天都有多个成功的 build。错误的 build 可以出现,但必须尽快得到修复。
          这样做的结果是你总能得到一个稳定的软件,它可能有一些 bug,但可以正常工作。每个人都基于相同的稳定代码进行开发,而且不会离得太远,否则就会不得不花很长时间集成回去。Bug被发现得越快,花在改正上的时间就越短。
    2. 持续集成实践
          从上面的故事我们大概了解了持续集成是如何在我们的日常工作中发挥作用的。但让一切正常运行起来还需要掌握更多的知识。我接下来会集中讲解一下高效持续集成的关键实践。
    2.1. 只维护一个源码仓库
          在软件项目里需要很多文件协调一致才能 build 出产品。跟踪所有这些文件是一项困难的工作,尤其是当有很多人一起工作时。所以,一点也不奇怪,软件开发者们这些年一直在研发这方面的工具。这些工具称为 源代码管理工具,或配置管理,或版本管理系统,或源码仓库,或各种其它名字。大部分开发项目中它们是不可分割的一部分。但可惜的是,并非所有项目都是如 此。虽然很罕见,但我确实参加过一些项目,它们直接把代码存到本地驱动器和共享目录中,乱得一塌糊涂。
          所以,作为一个最基本的要求,你必须有一个起码的源代码管理系统。成本不会是问题,因为有很多优秀的开源工具可用。当前较好的开源工具是 Subversion。(更老的同样开源的 CVS 仍被广泛使用,即使是 CVS 也比什么都不用强得多,但 Subversion 更先进也更强大。)有趣的是,我从与开发者们的交谈中了解到,很多商业源代码管理工具其实不比 Subversion 更好。只有一个商业软件是大家一致同意值得花钱的,这就是 Perforce。
          一旦你有了源代码管理系统,你要确保所有人都知道到哪里去取代码。不应出现这样的问题:“我应该到哪里去找xxx文件?” 所有东西都应该存在源码仓库里。
          即便对于用了源码仓库的团队,我还是观察到一个很普遍的错误,就是他们没有把所有东西都放在源码仓库里。一般人们都会把代码放进去,但还有许多其它文件, 包括测试脚本,配置文件,数据库Schema,安装脚本,还有第三方的库,所有这些build时需要的文件都应该放在源码仓库里。我知道一些项目甚至把编 译器也放到源码仓库里(用来对付早年间那些莫名其妙的C++编译器很有效)。一个基本原则是:你必须能够在一台干净的计算机上重做所有过程,包括 checkout和完全build。只有极少量的软件需要被预装在这台干净机器上,通常是那些又大又稳定,安装起来很复杂的软件,比如操作系统,Java 开发环境,或数据库系统。
          你必须把build需要的所有文件都放进源代码管理系统,此外还要把人们工作需要的其他东西也放进去。IDE配置文件就很适合放进去,因为大家共享同样的IDE配置可以让工作更简单。
          版本控制系统的主要功能之一就是创建 branch 以管理开发流。这是个很有用的功能,甚至可以说是一个基础特性,但它却经常被滥用。你最好还是尽量少用 branch。一般有一个mainline就够了,这是一条能反映项目当前开发状况的 branch。大部分情况下,大家都应该从mainline出发开始自己的工作。(合理的创建 branch 的理由主要包括给已发布的产品做维护和临时性的实验。)
          一般来说,你要把build依赖的所有文件放进代码管理系统中,但不要放build的结果。有些人习惯把最终产品也都放进代码管理系统中,我认为这是一种坏味道——这意味着可能有一些深层次的问题,很可能是无法可靠地重新build一个产品。
    2.2. 自动化build
          通常来说,由源代码转变成一个可运行的系统是一个复杂的过程,牵扯到编译,移动文件,将 schema 装载到数据库,诸如此类。但是,同软件开发中的其它类似任务一样,这也可以被自动化,也必须被自动化。要人工来键入各种奇怪的命令和点击各种对话框纯粹是 浪费时间,也容易滋生错误。
          在大部分开发平台上都能找到自动化 build 环境的影子。比如 make,这在 Unix 社区已经用了几十年了,Java 社区也开发出了 Ant,.NET 社区以前用 Nant,现在用 MSBuild。不管你在什么平台上,都要确保只用一条命令就可以运行这些脚本,从而 build 并运行系统。
          一个常见的错误是没有把所有事都放进自动化 build。比如:Build 也应该包括从源码仓库中取出数据库 schema 并在执行环境中设置的过程。我要重申一下前面说过的原则:任何人都应该能从一个干净的计算机上 check out 源代码,然后敲入一条命令,就可以得到能在这台机器上运行的系统。
          Build 脚本有很多不同的选择,依它们所属的平台和社区而定,但也没有什么定势。尽管大部分的 Java 项目都用 Ant,还是有一些项目用 Ruby(Ruby Rake 是一个不错的 build 脚本工具)。我们也曾经用 Ant 自动化早期的 Microsoft COM 项目,事实证明很有价值。
          一个大型 build 通常会很耗时,如果只做了很小的修改,你不会想花时间去重复所有的步骤。所以一个好的 build 工具应该会分析哪些步骤可以跳过。一个通用的办法是比较源文件和目标文件的修改时间,并只编译那些较新的源文件。处理依赖关系要麻烦一些:如果一个目标文 件修改了,所有依赖它的部分都要重新生成。编译器可能会帮你处理这些事情,也可能不会。
          根据你的需要,你可能会想 build 出各种不同的东西。你可以同时 build 系统代码和测试代码,也可以只 build 系统代码。一些组件可以被单独 build。Build 脚本应该允许你在不同的情况中 build 不同的 target。
          我们许多人都用 IDE,许多 IDE 都内置包含某种 build 管理功能。然而,相应的配置文件往往是这些 IDE 的专有格式,而且往往不够健壮,它们离了 IDE 就无法工作。如果只是 IDE 用户自己一个人开发的话,这还能够接受。但在团队里,一个工作于服务器上的主 build 环境和从其它脚本里运行的能力更重要。我们认为,在 Java 项目里,开发者可以用自己的 IDE 做 build,但主 build 必须用 Ant 来做,以保证它可以在开发服务器上运行。
    2.3. 让你的build自行测试
          传统意义上的 build 指编译,链接,和一些其它能让程序运行起来的步骤。程序可以运行并不意味着它也工作正常。现代静态语言可以在编译时检测出许多 bug,但还是有更多的漏网之鱼。
          一种又快又省的查 bug 的方法是在 build 过程中包含自动测试。当然,测试并非完美解决方案,但它确实能抓住很多 bug——多到可以让软件真正可用。极限编程(XP)和测试驱动开发(TDD)的出现很好地普及了自测试代码的概念,现在已经有很多人意识到了这种技巧的 价值。
          经常读我的著作的读者都知道我是 TDD 和 XP 的坚定追随者。但是我想要强调你不需要这两者中任何一个就能享受自测试代码的好处。两者都要求你先写测试,再写代码以通过测试,在这种工作模式里测试更多 着重于探索设计而不是发现 bug。这绝对是一个好方法,但对于持续集成而言它并不必要,因为这里对自测试代码的要求没有那么高。(尽管我肯定会选择用 TDD 的方式。)
          自测试代码需要包含一套自动化测试用例,这些测试用例可以检查大部分代码并找出 bug。测试要能够从一条简单的命令启动。测试结果必须能指出哪些测试失败了。对于包含测试的 build,测试失败必须导致 build 也失败。
          在过去的几年里,TDD 的崛起普及了开源的 XUnit 系列工具,这些工具用作以上用途非常理想。对于我们在 ThoughWorks 工作的人来说,XUnit 工具已经证明了它们的价值。我总是建议人们使用它们。这些最早由 Kent Beck 发明的工具使得设置一个完全自测试环境的工作变得非常简单。
          毋庸置疑,对于自动测试的工作而言,XUnit 工具只是一个起点。你还必须自己寻找其他更适合端对端测试的工具。现在有很多此类工具,包括FIT,Selenium,Sahi,Watir,FITnesse,和许多其它我无法列在这里的工具。
          当然你不能指望测试发现所有问题。就像人们经常说的:测试通过不能证明没有 bug。然而,完美并非是你要通过自测试 build 达到的唯一目标。经常运行不完美的测试要远远好过梦想着完美的测试,但实际什么也不做。
    2.4. 每人每天都要向 mainline 提交代码
          集成的主要工作其实是沟通。集成可以让开发者告诉其他人他们都改了什么东西。频繁的沟通可以让人们更快地了解变化。
          让开发者提交到 mainline 的一个先决条件是他们必须能够正确地 build 他们的代码。这当然也包括通过 build 包含的测试。在每个提交迭代里,开发者首先更新他们的工作拷贝以与 mainline 一致,解决任何可能的冲突,然后在自己的机器上做 build。在 build 通过后,他们就可以随便向 mainline 提交了。
          通过频繁重复上述过程,开发者可以发现两个人之间的代码冲突。解决问题的关键是尽早发现问题。如果开发者每过几个小时就会提交一次,那冲突也会在出现的几 个 小时之内被发现,从这一点来说,因为还没有做太多事,解决起来也容易。如果让冲突待上几个星期,它就会变得非常难解决。
          因为你在更新工作拷贝时也会做 build,这意味着你除了解决源代码冲突外也会检查编译冲突。因为 build 是自测试的,你也可以查出代码运行时的冲突。后者如果在一段较长的时间还没被查出的话会变得尤其麻烦。因为两次提交之间只有几个小时的修改,产生这些问题 只可能在很有限的几个地方。此外,因为没改太多东西,你还可以用 diff-debugging 的技巧来找 bug。
          总的来说,我的原则是每个开发者每天都必须提交代码。实践中,如果开发者提交的更为频繁效果也会更好。你提交的越多,你需要查找冲突错误的地方就越少,改起来也越快。
          频繁提交客观上会鼓励开发者将工作分解成以小时计的小块。这可以帮助跟踪进度和让大家感受到进展。经常会有人一开始根本无法找到可以在几小时内完成的像样的工作,但我们发现辅导和练习可以帮助他们学习其中的技巧。
          每次提交都应在集成计算机上重新构建 mainline
          使用每日提交的策略后,团队就能得到很多经过测试的 build。这应该意味着 mainline 应该总是处于一种健康的状态。但在实践中,事情并非总是如此。一个原因跟纪律有关,人们没有严格遵守在提交之前在本地更新并做 build 的要求。另一个原因是开发者的计算机之间环境配置的不同。
          结论是你必须保证日常的 build 发生在专用的集成计算机上,只有集成 build 成功了,提交的过程才算结束。本着“谁提交,谁负责”的原则,开发者必须监视 mainline 上的 build 以便失败时及时修复。一个推论是如果你在下班前提交了代码,那你在 mainline build 成功之前就不能回家。
    我知道主要有两种方法可以使用:手动 build,或持续集成服务器软件。
          手动 build 描述起来比较简单。基本上它跟提交代码之前在本地所做的那次 build 差不多。开发者登录到集成计算机,check out 出 mainline 上最新的源码(已包含最新的提交),并启动一个集成 build。他要留意 build 的进程,只有 build 成功了他的提交才算成功。(请查看 Jim Shore 的描述。)
          持续集成服务器软件就像一个监视着源码仓库的监视器。每次源码仓库中有新的提交,服务器就会自动 check out 出源代码并启动一次 build,并且把 build 的结果通知提交者。这种情况下,提交者的工作直到收到通知(通常是 email)才算结束。
          在 ThoughtWorks,我们都是持续集成服务器软件的坚定支持者,实际上我们引领了 CruiseControl 和 CruiseControl.NET 最早期的开发,两者都是被广泛使用的开源软件。此后,我们还做了商业版的 Cruise 持续集成服务器。我们几乎在每一个项目里都会用持续集成服务器,并且对结果非常满意。
    不 是每个人都会用持续集成服务器。Jim Shore 就清楚地表达了为什么他更偏好手动的办法。我同意他的看法中的持续集成并不仅仅是安装几个软件而已,所有的实践都必须为了能让持续集成更有效率。但同样 的,许多持续集成执行得很好的团队也会发现持续集成服务器是个很有用的工具。
          许多组织根据安排好的日程表做例行 build,如每天晚上。这其实跟持续集成是两码事,而且做得远远不够。持续集成的最终目标就是要尽可能快地发现问题。Nightly build 意味着 bug 被发现之前可能会待上整整一天。一旦 bug 能在系统里呆这么久,找到并修复它们也会花较长的时间。
          做好持续集成的一个关键因素是一旦 mainline 上的 build 失败了,它必须被马上修复。而在持续集成环境中工作最大的好处是,你总能在一个稳定的基础上做开发。mainline 上 build 失败并不总是坏事,但如果它经常出错,就意味着人们没有认真地在提交代码前先在本地更新代码和做 build。当 mainline 上 build 真的失败时,第一时间修复就成了头等大事。为了防止在 mainline 上的问题,你也可以考虑用 pending head 的方法。
          当团队引入持续集成时,这通常是最难搞定的事情之一。在初期,团队会非常难以接受频繁在mainline 上做 build 的习惯,特别当他们工作在一个已存在的代码基础上时更是如此。但最后耐心和坚定不移的实践常常会起作用,所以不要气馁。
    2.5. 保持快速 build
          持续集成的重点就是快速反馈。没有什么比缓慢的 build 更能危害持续集成活动。这里我必须承认一个奇思怪想的老家伙关于 build 快慢标准的的玩笑(译者注:原文如此,不知作者所指)。我的大部分同事认为超过1小时的 build 是不能忍受的。团队们都梦想着把 build 搞得飞快,但有时我们也确实会发现很难让它达到理想的速度。
          对大多数项目来说,XP 的10分钟 build 的指导方针非常合理。我们现在做的大多数项目都能达到这个要求。这值得花些力气去做,因为你在这里省下的每一分钟都能体现在每个开发者每次提交的时候。持 续集成要求频繁提交,所以这积累下来能节省很多时间。如果你一开始就要花1小时的时间做 build,想加快这个过程会相当有挑战。即使在一个从头开始的新项目里,想让 build 始终保持快速也是很有挑战的。至少在企业应用里,我们发现常见的瓶颈出现在测试时,尤其当测试涉及到外部服务如数据库。
          也许最关键的一步是开始使用分阶段build(staged build)。分阶段 build(也被称作 build 生产线)的基本想法是多个 build 按一定顺序执行。向 mainline 提交代码会引发第一个 build,我称之为提交 build(commit build)。提交 build 是当有人向 mainline 提交时引发的 build。提交 build 要足够快,因此它会跳过一些步骤,检测 bug 的能力也较弱。提交 build 是为了平衡质量检测和速度,因此一个好的提交 build 至少也要足够稳定以供他人基于此工作。
          一旦提交 build 成功,其他人就可以放心地基于这些代码工作了。但别忘了你还有更多更慢的测试要做,可以另找一台计算机来运行运行这些测试。
    一 个简单的例子是两阶段 build。第一阶段会编译和运行一些本地测试,与数据库相关的单元测试会被完全隔离掉(stub out)。这些测试可以运行得非常快,符合我们的10分钟指导方针。但是所有跟大规模交互,尤其是真正的数据库交互的 bug 都无法被发现。第二阶段的 build 运行一组不同的测试,这些测试会调用真正的数据库并涉及更多的端到端的行为。这些测试会跑上好几小时。
          这种情况下,人们用第一阶段作为提交 build,并把这作为主要的持续集成工作。第二阶段 build 是次级build,只有在需要的时候才运行,从最后一次成功的提交 build 中取出可执行文件作进一步测试。如果次级 build 失败了,大家不会立刻停下手中所有工作去修复,但团队也要在保证提交 build 正常运行的同时尽快修正 bug。实际上次级 build 并非一定要正常运行,只要 bug 都能够被检查出来并且能尽快得到解决就好。在两阶段 build 的例子里,次级 build 经常只是纯粹的测试,因为通常只是测试拖慢了速度。
          如果次级 build 检查到了 bug,这是一个信号,意味着提交 build 需要添加一个新测试了。你应该尽可能把次级 build 失败过的测试用例都添加到提交 build 中,使得提交 build 有能力验证这些 bug。每当有 bug 绕过提交测试,提交测试总能通过这种方法被加强。有时候确实无法找到测试速度和 bug 验证兼顾的方法,你不得不决定把这个测试放回到次级 build 里。但大部分情况下都应该可以找到合适加入提交 build 的测试。
          上面这个例子是关于两阶段 build,但基本原则可以被推广到任意数量的后阶段 build。提交 build 之后的其它 build 都可以同时进行,所以如果你的次级测试要两小时才能完成,你可以通过用两台机器各运行一半测试来快一点拿到结果。通过这个并行次级 build 技巧,你可以向日常 build 流程中引入包括性能测试在内的各种自动化测试。(当我过去几年内参加 Thoughtworks 的各种项目时,我碰到了很多有趣的技巧,我希望能够说服一些开发者把这些经验写出来。)
    2.6. 在模拟生产环境中进行测试
          测试的关键在于在受控条件下找出系统内可能在实际生产中出现的任何问题。这里一个明显的因素是生产系统的运行环境。如果你不在生产环境做测试,所有环境差异都是风险,可能最终造成测试环境中运行正常的软件在生产环境中无法正常运行。
          自然你会想到建立一个与生产环境尽可能完全相同的测试环境。用相同的数据库软件,还要同一个版本;用相同版本的操作系统;把所有生产环境用到的库文件都放进测试环境中,即使你的系统没有真正用到它们;使用相同的IP地址和端口;以及相同的硬件;
          好吧,现实中还是有很多限制的。如果你在写一个桌面应用软件,想要模拟所有型号的装有不同第三方软件的台式机来测试显然是不现实的。类似的,有些生产环境 可 能因为过于昂贵而无法复制(尽管我常碰到出于经济考虑拒绝复制不算太贵的环境,结果得不偿失的例子)。即使有这些限制,你的目标仍然是尽可能地复制生产环 境,并且要理解并接受因测试环境和生产环境不同带来的风险。
          如果你的安装步骤足够简单,无需太多交互,你也许能在一个模拟生产环境里运行提交 build。但事实上系统经常反应缓慢或不够稳定,这可以用 test double 来解决。结果常常是提交测试为了速度原因在一个假环境内运行,而次级测试运行在模拟真实的生产环境中。
          我注意到越来越多人用虚拟化来搭建测试环境。虚拟机的状态可以被保存,因此安装并测试最新版本的build相对简单。此外,这可以让你在一台机器上运行多个测试,或在一台机器上模拟网络里的多台主机。随着虚拟化性能的提升,这种选择看起来越来越可行。
    2.7. 让每个人都能轻易获得最新的可执行文件
          软件开发中最困难的部分是确定你的软件行为符合预期。我们发现事先清楚并正确描述需求非常困难。对人们而言,在一个有缺陷的东西上指出需要修改的地方要容易得多。敏捷开发过程认可这种行为,并从中受益。
          为了以这种方式工作,项目中的每个人都应该能拿到最新的可执行文件并运行。目的可以为了 demo,也可以为了探索性测试,或者只是为了看看这周有什么进展。
          这做起来其实相当简单:只要找到一个大家都知道的地方来放置可执行文件即可。可以同时保存多份可执行文件以备使用。每次放进去的可执行文件应该要通过提交测试,提交测试越健壮,可执行文件就会越稳定。
          如果你采用的过程是一个足够好的迭代过程,把每次迭代中最后一个 build 放进去通常是明智的决定。Demo 是一个特例,被 demo 的软件特性都应该是演示者熟悉的特性。为了 demo 的效果值得牺牲掉最新的 build,转而找一个早一点但演示者更熟悉的版本。
    2.8. 每个人都能看到进度
          持续集成中最重要的是沟通。你需要保证每个人都能轻易看到系统的状态和最新的修改。
          沟通的最重要的途径之一是 mainline build。如果你用 Cruise,一个内建的网站会告诉你是否正有 build 在进行,和最近一次 mainline build 的状态。许多团队喜欢把一个持续工作的状态显示设备连接到 build 系统来让这个过程更加引人注目,最受欢迎的显示设备是灯光,绿灯闪亮表示 build 成功,红灯表示失败。一种常见的选择是红色和绿色的熔岩灯,这不仅仅指示 build 的状态,还能指示它停留在这个状态的时间长短,红灯里出现气泡表示 build 出问题已经太长时间了。每一个团队都会选择他们自己的 build 传感器。如果你的选择带点幽默性和娱乐性效果会更好(最近我看到有人在实验跳舞兔)。
          即使你在使用手动持续集成,可见程度依然很重要。Build 计算机的显示器可以用来显示 mainline build 的状态。你很可能需要一个 build 令牌放在正在做 build 那人的桌子上(橡皮鸡这种看上去傻傻的东西最好,原因同上)。有时人们会想在 build 成功时弄出一点噪音来,比如摇铃的声音。
          持续集成服务器软件的网页可以承载更多信息。Cruise 不仅显示谁在做 build,还能指出他们都改了什么。Cruise 还提供了一个历史修改记录,以便团队成员能够对最近项目里的情况有所了解。我知道 team leader喜欢用这个功能了解大家手头的工作和追踪系统的更改。
          使用网站的另一大优点是便于那些远程工作的人了解项目的状态。一般来说,我倾向于让项目中发挥作用的成员都坐在一起工作,但通常也会有一些外围人员想要了 解项目的动态。如果组织想要把多个项目的 build情况聚合起来以提供自动更新的简单状态时,这也会很有用。
          好的信息展示方式不仅仅依赖于电脑显示器。我最喜欢的方式出现于一个中途转入持续集成的项目。很长时间它都无法拿出一个稳定的 build。我们在墙上贴了一整年的日历,每一天都是一个小方块。每一天如果 QA 团队收到了一个能通过提交测试的稳定 build,他们都会贴一张绿色的贴纸,否则就是红色的贴纸。日积月累,从日历上能看出 build 过程在稳定地进步。直到绿色的小方块已经占据了大部分的空间时,日历被撤掉了,因为它的使命已经完成了。
    2.9. 自动化部署
          自动化集成需要多个环境,一个运行提交测试,一个或多个运行次级测试。每天在这些环境之间频繁拷贝可执行文件可不轻松,自动化是一个更好的方案。为实现自 动 化,你必须有几个帮你将应用轻松部署到各个环境中的脚本。有了脚本之后,自然而然的结果是你也要用类似的方式部署到生产环境中。你可能不需要每天都部署到 生产环境(尽管我见过这么做的项目),但自动化能够加快速度并减少错误。它的代价也很低,因为它基本上和你部署到测试环境是一回事。
          如果你部署到生产环境,你需要多考虑一件事情:自动化回滚。坏事情随时可能发生,如果情况不妙,最好的办法是尽快回到上一个已知的正常状态。能够自动回滚 也会减轻部署的压力,从而鼓励人们更频繁地部署,使得新功能更快发布给用户。(Ruby on Rails 社区开发了一个名为 Capistrano 的工具,是这类工具很好的代表。)
          我还在服务器集群环境中见过滚动部署的方法,新软件每次被部署到一个节点上,在几小时时间内逐步替换掉原有的软件。

    参见相关文章:进化式数据库设计
          许多人在频繁发布时都遇到的一个障碍是数据库的迁移。数据库的改动很麻烦,因为你不能仅仅修改 schema,你还要保证数据本身也被顺利迁移。这篇文章描述了我的同事 Pramod Sadalage 自动化重构和迁移数据库的技巧。这篇文章只是一个简单的记录,其中的信息在 Pramod 和 Scott Amblers 关于数据库重构的书中有更详细的阐述[ambler-sadalage]。
          在web 应用开发中,我碰到的一个有趣的想法是把一个试验性的 build 部署到用户的一个子集。团队可以观察这个试验 build 被使用的情况,以决定是否将它部署到全体用户。你可以在做出最终决定之前试验新的功能和新的 UI。自动化部署加上良好的持续集成的纪律是这项工作的基础。
    3. 持续集成的益处
          我认为持续集成最显著也最广泛的益处是降低风险。说到这里,我的脑海中还是会浮现出第一段描述的早期软件项目。他们已经到了一个漫长项目的末期(至少他们期望如此),但还是不知道距离真正的结束有多远。
          延迟集成的问题在于时间难以估计,你甚至无法得知你的进展。结果是你在项目最紧张的阶段之一把自己置入了一个盲区,此时即使没有拖延(这很罕见)也轻松不了多少。
    持续集成巧妙的解决了这个问题。长时间的集成不再存在,盲区被彻底消除了。在任何时间你都知道你自己的进展,什么能运转,什么不能运转,你系统里有什么明显的 bug,这些都一目了然。
          Bug 让人恶心,它摧毁人的自信,搞乱时间表,还破坏团队形象。已部署软件里的 bug 招致用户的怒气。未完成软件里的 bug 让你接下来的开发工作受阻。
          持续集成不能防止 bug 的产生,但它能明显让寻找和修改 bug 的工作变简单。从这个方面看,它更像自测试代码。如果你引入 bug 后能很快发现,改正也会简单得多。因为你只改了系统中很小的一部分,你无需看很多代码就能找到问题所在。因为这一小部分你刚刚改过,你的记忆还很新鲜,也 会让找 bug 的工作简单不少。你还可以用差异调试——比较当前版本和之前没有 bug 的版本。
          Bug 也会积累。你的 bug 越多,解决掉任何一个都会越困难。这部分原因是 bug 之间的互相作用,你看到的失败实际上是多个问题叠加的结果,这使得检查其中任何一个问题都更加困难。还有部分原因是心理层面的因素,当人们面对大量 bug 时,他们寻找和解决 bug 的动力就会减弱。《Pragmatic Programmer》一书中称之为“破窗综合症“。
    使 用持续集成的项目的通常结果是 bug 数目明显更少,不管在产品里还是开发过程中都是如此。然而,我必须强调,你受益的程度跟你测试的完善程度直接相关。其实建立测试系统并非想象中那么困难, 但带来的区别却显而易见。一般来说,团队需要花一定时间才能把 bug 数量减少到理想的地步。做到这一点意味着不断添加和改进测试代码。
          如果你用了持续集成,你就解决了频繁部署的最大障碍之一。频繁部署很有价值,因为它可以让你的用户尽快用到新功能,从而快速提供反馈,这样他们在开发过程中可以有更多的互动。这可以帮助打破我心目中成功的软件开发最大的障碍——客户与开发团队之间的障碍。
    4. 引入持续集成
          看到这里,你一定想要尝试一下持续集成了。但是从哪里开始呢?我在上面描述了一整套的实践,这些可以让你体验到所有的好处。但你也不必一开始就照单全收, 你 有自己的选择余地,基本上取决于你的环境和团队的特性。我们也从过去的实践中吸取了一些经验和教训,做好下面这些事会对于你的持续集成运作有重要的意义。
          最早的几步之一是实现 build 自动化。把所有需要的东西都放进版本控制系统里,这样你就可以用一条命令 build 整个系统。这对许多项目而言不是什么小任务,这对于其他东西正常工作非常重要。刚开始你可能只是偶尔需要的时候做一个 build,或者只是做一个自动的 nightly build。当你还没有开始持续集成时,自动 nightly build 也是一个不错的开始。
          其次是引入一些自动化测试到你的 build 中。试着指出主要出错的地方,并要让自动化测试暴露这些错误。建立又快又好的测试集合会比较困难,特别在已存在的项目中,建立测试需要时间。你必须找个地方开始动手,就像俗话说的,罗马不是一天建成的。
          还要试着加快提交 build 的速度。虽然需要几个小时 build 的持续集成也比什么都没有好,但能做到传说中的10分钟会更好。这通常要对你的代码动一些大手术,以剥离对运行缓慢那部分的依赖。
          如果你刚刚开始一个新项目,从一开始就用持续集成。对build时间保持关注,当慢于10分钟时就立即采取行动。通过快速行动,你可以在代码变得太大之前做一些必要的架构调整。
          比所有事情都重要的是寻找帮助。找一个以前做过持续集成的人来帮你。像所有新技巧一样,当你不知道最终结果怎样的时候会非常难以实施。请一个导师 (mentor)可能会花些钱,但如果你不做,你会付出时间和生产效率损失的代价。(免责声明/广告:是的,我们 ThoughtWorks 在这个领域提供咨询服务。不管怎样,我们曾经犯过你可能会犯的大多数错误。)
    5. 最后的思考
          在我和 Matt 写完最初那篇论文后的几年,持续集成已经成为了软件开发的一个主流方法。ThoughtWorks 的项目很少有不用到它的。我们也能看到世界各地的人们在用持续集成。与极限编程中一些充满争议的实践不同,我很少听到关于持续集成的负面消息。
          如果你还没用持续集成,我强烈建议你试一下。如果你已经在做了,可能这篇文章中的某些方法可以帮你得到更好的效果。过去几年中,我们已经了解了很多关于持续集成的知识,我希望还有更多的知识可以让我们学习和提高。
    6. 延伸阅读
          本文篇幅所限,只能覆盖部分内容。想了解持续集成的更多细节,我建议看一下 Paul Duvall 的书(这本书得到了Jolt大奖)。目前为止还没有多少关于分阶段build的文章,但有一篇 Dave Farley 发表在《ThoughtWorks 文选》中的文章还不错(你也可以在这里找到)。

    分类: 持续集成

    持续集成(第一版)

    --Martin Fowler & Matthew Foemmel著 透明 译
    英文原文版权由Martin Fowler拥有
    Original text is copyrighted by Martin Fowler
    原文链接:http://martinfowler.com/articles/continuousIntegration.html


        在任何软件开发过程中都有一个重要的部分:得到可靠的软件创建(build)版本。尽管知道创建的重要性,但是我们仍然会经常因为创建失败而惊讶不已。在 这篇文章里,我们将讨论Matt(Matthew Foemmel)在ThoughtWorks的一个重要项目中实施的过程,这个过程在我们的公司里日益受到重视。它强调完全自动化的、可重复的创建过程, 其中包括每天运行多次的自动化测试。它让开发者可以每天进行系统集成,从而减少了集成中的问题。
         ThoughtWorks公司已经开放了CruiseControl软件的源代码,这是一个自动化持续集成的工具。此外,我们还提供CruiseControl、Ant和持续集成方面的顾问服务。如果需要更多的信息,请与Josh Mackenzie(jmackenz@ThoughtWorks.com)联系。
    本文有以下主要内容:

    • 持续集成的优点
    • 集成越频繁,效果越好
    • 一次成功的创建是什么样的?
    • 单一代码源
    • 自动化创建脚本
    • 自测试的代码
    • 主创建
    • 代码回归
    • 总结

          在软件开发的领域里有各种各样的“最佳实践”,它们经常被人们谈起,但是似乎很少有真正得到实现的。这些实践最基本、最有价值的就是:都有一个完全自动化的创建、测试过程,让开发团队可以每天多次创建他们的软件。“日 创建”也是人们经常讨论的一个观点,McConnell在他的《快速软件开发》中将日创建作为一个最佳实践来推荐,同时日创建也是微软很出名的一项开发方 法。但是,我们更支持XP社群的观点:日创建只是最低要求。一个完全自动化的过程让你可以每天完成多次创建,这是可以做到的,也是完全值得的。
          在这里,我们使用了“持续集成(Continuous Integration)”这个术语,这个术语来自于XP(极限编程)的一个实践。但是我们认为:这个实践早就存在,并且很多并没有考虑XP的人也在使用 着它。只不过我们一直用XP作为软件开发过程的标准,XP也对我们的术语和实践产生了深远的影响。尽管如此,你还是可以只使用持续集成,而不必使用XP的 任何其他部分——实际上,我们认为:对于任何切实可行的软件开发活动,持续集成都是很基本的组成部分。
    实现自动化日创建需要做以下几部分的工作:

    • 将所有的源代码保存在单一的地点,让所有人都能从这里获取最新的源代码(以及以前的版本)。
    • 使创建过程完全自动化,让任何人都可以只输入一条命令就完成系统的创建。
    • 使测试完全自动化,让任何人都可以只输入一条命令就运行一套完整的系统测试。
    • 确保所有人都可以得到最新、最好的可执行文件。
    • 所有这些都必须得到制度的保证。我们发现,向一个项目中引入这些制度需要耗费相当大的精力。但是,我们也发现,一旦制度建立起来,保持它的正常运转就不需要花多少力气了。


    1. 持续集成的优点
          描述持续集成最大的难点在于:它从根本上改变了整个开发模式。如果没有在持续集成的实践环境中工作过,你很难理解它的开发模式。实际上,在单独工作的时 候,绝大多数人都能感觉到这种气氛——因为他们只需要与自己的系统相集成。对于许多人来说,“团队开发”这个词总让他们想起软件工程领域中的一些难题。持 续集成减少了这些难题的数量,代之以一定的制度。
          持续集成最基本的优点就是:它完全避免了开发者们的“除虫会议”——以前开发者们经常需要开这样的会,因为某个人在工作的时候踩进了别人的领域、影响了别 人的代码,而被影响的人还不知道发生了什么,于是bug就出现了。这种bug是最难查的,因为问题不是出在某一个人的领域里,而是出在两个人的交流上面。 随着时间的推移,问题会逐渐恶化。通常,在集成阶段出现的bug早在几周甚至几个月之前就已经存在了。结果,开发者需要在集成阶段耗费大量的时间和精力来 寻找这些bug的根源。
          如果使用持续集成,这样的bug绝大多数都可以在引入的同一天就被发现。而且,由于一天之中发生变动的部分并不多,所以可以很快找到出错的位置。如果找不 到bug究竟在哪里,你也可以不把这些讨厌的代码集成到产品中去。所以,即使在最坏的情况下,你也只是不添加引起bug的特性而已。(当然,可能你对新特 性的要求胜过了对bug的憎恨,不过至少你可以多一种选择。)
          到现在为止,持续集成还不能保证你抓到所有集成时出现的bug。持续集成的排错能力取决于测试技术,众所周知,测试无法证明已经找到了所有的错误。关键是在于:持续集成可以及时抓到足够多的bug,这就已经值回它的开销了。
          所以,持续集成可以减少集成阶段“捉虫”消耗的时间,从而最终提高生产力。尽管现在还不知道是否有人对这种方法进行过科学研究,但是作为一种实践性的方 法,很明显它是相当有效的。持续集成可以大幅减少耗费在“集成地狱”中的时间,实际上,它可以把地狱变成小菜一碟。


    2. 集成越频繁,效果越好
          持续集成有一个与直觉相悖的基本要点:经常性的集成比很少集成要好。对于持续集成的实践者来说,这是很自然的;但是对于从未实践过持续集成的人来说,这是与直观印象相矛盾的。
          如果你的集成不是经常进行的(少于每天一次),那么集成就是一件痛苦的事情,会耗费你大量的时间与精力。我们经常听见有人说:“在一个大型的项目中,不能应用日创建”,实际上这是一种十分愚蠢的观点。
          不过,还是有很多项目实践着持续集成。在一个五十人的团队、二十万行代码的项目中,我们每天要集成二十多次。微软在上千万行代码的项目中仍然坚持日创建。
          持续集成之所以可行,原因在于集成的工作量是与两次集成间隔时间的平方成正比的。尽管我们还没有具体的衡量数据,但是可以大概估计出来:每周集成一次所需 的工作量绝对不是每天集成的5倍,而是大约25倍。所以,如果集成让你感到痛苦,也许就说明你应该更频繁地进行集成。如果方法正确,更频繁的集成应该能减 少你的痛苦,让你节约大量时间。
          持续集成的关键是自动化。绝大多数的集成都可以而且应该自动完成。读取源代码、编译、连接、测试,这些都可以自动完成。最后,你应该得到一条简单的信息, 告诉你这次创建是否成功:“yes”或“no”。如果成功,本次集成到此为止;如果失败,你应该可以很简单地撤消最后一次的修改,回到前一次成功的创建。 在整个创建过程中,完全不需要你动脑子。
          如果有了这样一套自动化过程,你随便想多频繁进行创建都可以。唯一的局限性就是创建过程本身也会消耗一定的时间。(译注:不过与捉虫所需的时间比起来,这点时间是微不足道的。)


    3. 一次成功的创建是什么样的?
          有一件重要的事需要确定:怎样的创建才算是成功的?看上去很简单,但是如此简单的事情有时却会变得一团糟,这是值得注意的。有一次,Martin Fowler去检查一个项目。他问这个项目是否执行日创建,得到了肯定的回答。幸亏Ron Jeffries也在场,他又提了一个问题:“你们如何处理创建错误?”回答是:“我们给相关的人发一个e-mail。”实际上,这个项目已经好几个月没 有得到成功的创建了。这不是日创建,这只是日创建的尝试。
          对于下列“成功创建”的标准,我们还是相当自信的:

    • 所有最新的源代码都被配置管理系统验证合格
    • 所有文件都通过重新编译
    • 得到的目标文件(在我们这里就是Java的class文件)都通过连接,得到可执行文件
    • 系统开始运行,针对系统的测试套件(在我们这里大概有150个测试类)开始运行
    • 如果所有的步骤都没有错误、没有人为干涉,所有的测试也都通过了,我们就得到了一个成功的创建

          绝大多数人都认为“编译+连接=创建”。至少我们认为:创建还应该包括启动应用程序、针对应用程序运行简单测试(McConnell称之为“冒烟测试”:打开开关让软件运行,看它是否会“冒烟”)。运行更详尽的测试集可以大大提高持续集成的价值,所以我们会首选更详尽的测试。


    4. 单一代码源
          为了实现每日集成,任何开发者都需要能够很容易地获取全部最新的源代码。以前,如果要做一次集成,我们就必须跑遍整个开发中心,询问每一个程序员有没有新的代码,然后把这些新代码拷贝过来,再找到合适的插入位置……没有什么比这更糟糕的了。
          办法很简单。任何人都应该可以带一台干净的机器过来,连上局域网,然后用一条命令就得到所有的源文件,马上开始系统的创建。
    最 简单的解决方案就是:用一套配置管理(源代码控制)系统作为所有代码的来源。配置管理系统通常都设计有网络功能,并且带有让开发者轻松获取源代码的工具。 而且,它们还提供版本管理工具,这样你可以很轻松地找到文件以前的版本。成本就更不成问题了,CVS就是一套出色的开放源代码的配置管理工具。
          所有的源文件都应该保存在配置管理系统中。我说的这个“所有”常常比人们想到的还要多,它还包括创建脚本、属性文件、数据库调度DLL、安装脚本、以及在 一台干净的机器上开始创建所需的其他一切东西。经常都能看到这样的情况:代码得到了控制,但是其他一些重要的文件却找不到了。
          尽量确保所有的东西都保存在配置管理系统的同一棵代码源树中。有时候为了得到不同的组件,人们会使用配置管理系统中不同的项目。这带来的麻烦就是:人们不 得不记住哪个组件的哪个版本使用了其他组件的哪些版本。在某些情况下,你必须将代码源分开,但是这种情况出现的几率比你想象的要小得多。你可以在从一棵代 码源树创建多个组件,上面那些问题可以通过创建脚本来解决,而不必改变存储结构。


    5. 自动化创建脚本
          如果你编写的是一个小程序,只有十几个文件,那么应用程序的创建可能只是一行命令的事:javac *.java。更大的项目就需要更多的创建工作:你可能把文件放在许多目录里面,需要确保得到的目标代码都在适当的位置;除了编译,可能还有连接的步骤; 你可能还从别的文件中生成了代码,在编译之前需要先生成;测试也需要自动运行。
          大规模的创建经常会耗费一些时间,如果只做了一点小小的改动,当然你不会希望重新做所有这些步骤。所以好的创建工具会自动分析需要改变的部分,常见的方法 就是检查源文件和目标文件的修改日期,只有当源文件的修改日期迟于目标文件时,才会重新编译。于是,文件之间的依赖就需要一点技巧了:如果一个目标文件发 生了变化,那么只有那些依赖它的目标文件才会重新编译。编译器可能会处理这类事情,也可能不会。
    取决于自己的需要,你可以选择不同的创建类型:你创建的系统可以有测试代码,也可以没有,甚至还可以选择不同的测试集;一些组件可以单独创建。创建脚本应该让你可以根据不同的情况选择不同的创建目标。
          你输入一行简单的命令之后,帮你挑起这副重担常常是脚本。你使用的可能是shell脚本,也可能是更复杂的脚本语言(例如Perl或Python)。但是很快你就会发现一个专门设计的创建环境是很有用的,例如Unix下的make工具。
          在我们的Java开发中,我们很快就发现需要一个更复杂的解决方案。Matt用了相当多的时间开发了一个用于企业级Java开发的创建工具,叫做Jinx。但是,最近我们已经转而使用开放源代码的创建工具Ant(http://jakarta.apache.org/ant/index.html)。Ant的设计与Jinx非常相似,也支持Java文件编译和Jar封装。同时,编写Ant的扩展也很容易,这让我们可以在创建过程中完成更多的任务。
          许多人都使用IDE,绝大多数的IDE中都包含了创建管理的功能。但是,这些文件都依赖于特定的IDE,而且经常比较脆弱,而且还需要在IDE中才能工 作。IDE的用户可以建立自己的项目文件,并且在自己的单独开发中使用它们。但是我们的主创建过程用Ant建立,并且在一台使用Ant的服务器上运行。


    6. 自测试的代码
          只让程序通过编译还是远远不够的。尽管强类型语言的编译器可以指出许多问题,但是即使成功通过了编译,程序中仍然可能留下很多错误。为了帮助跟踪这些错误,我们非常强调自动化测试——这也是XP提倡的另一个实践。
          XP将测试分为两类:单元测试和容纳测试(也叫功能测试)。单元测试是由开发者自己编写的,通常只测试一个类或一小组类。容纳测试通常是由客户或外部的测 试组在开发者的帮助下编写的,对整个系统进行端到端的测试。这两种测试我们都会用到,并且尽量提高测试的自动化程度。
          作为创建的一部分,我们需要运行一组被称为“BVT”(Build Verification Tests,创建确认测试)的测试。BVT中所有的测试都必须通过,然后我们才能宣布得到了一个成功的创建。所有XP风格的单元测试都属于BVT。由于本 文是关于创建过程的,所以我们所说的“测试”基本上都是指BVT。请记住,除了BVT之外,还有一条测试线存在(译注:指功能测试),所以不要把BVT和 整体测试、QA等混为一谈。实际上,我们的QA小组根本不会看到没有通过BVT的代码,因为他们只对成功的创建进行测试。
          有一条基本的原则:在编写代码的同时,开发者也应该编写相应的测试。完成任务之后,他们不但要归还(check in)产品代码,而且还要归还这些代码的测试。这也跟XP的“测试第一”的编程风格很相似:在编写完相应的测试、并看到测试失败之前,你不应该编写任何代 码。所以,如果想给系统添加新特性,你首先应该编写一个测试。只有当新的特性已经实现了以后,这个测试才可能通过。然后,你的工作就是让这个测试能够通 过。
          我们用Java编写这些测试,与开发使用同样的语言,所以编写测试与编写代码没有太大的区别。我们使用JUnit(http://www.junit.org/)来作为组织、编写测试的框架。JUnit是一个简单的框架,让我们可以快速编写测试、将测试组织为套件、并以交互或批处理的模式来运行测试套件。(JUnit是xUnit家族的Java版本——xUnit包括了几乎所有语言的测试框架。)
          在编写软件的过程中,在每一次的编译之后,开发者通常都会运行一部分单元测试。这实际上提高了开发者的工作效率,因为这些单元测试可以帮助你发现代码中的 逻辑错误。然后,你就没必要去调试查错,只需要注意最后一次运行测试之后修改的代码就行了。这个修改的范围应该很小,所以寻找bug也就容易多了。
    并 非所有的人都严格遵循XP“测试第一”的风格,但是在第一时间编写测试的好处是显而易见的。它们不但让每个人的工作效率更高,而且由这些测试构成的BVT 更能捕捉到系统中的错误。因为BVT每天要运行好几次,所以BVT检查出的任何问题都是比较容易改正的,原因很简单:我们只做了相当小范围的修改,所以我 们可以在这个范围内寻找bug。在修改过的一小块代码中排错当然比跟踪整个系统来排错要有效多了。
          当然,你不能指望测试帮你找到所有的问题。就象人们常说的:测试不能证明系统中不存在错误。但是,尽善尽美不是我们唯一的要求。不够完美的测试只要经常运行,也比永远写不出来的“完美测试”要好得多。
          另一个相关的问题就是:开发者们为自己的代码编写测试。我们经常听人说:开发者不应该测试自己的代码,因为他们很容易忽视自己工作中的错误。尽管这也是事 实,但是自测试过程需要快速将测试转入代码基础中。这种快速转换的价值超过独立测试者的价值。所以,我们还是用开发者自己编写的测试来构造BVT,但是仍 然有独立编写的容纳测试。
          自测试另一个很重要的部分就是它通过反馈——XP的一项核心价值——来提高测试的质量。这里的反馈来自于从BVT中逃脱的bug。自测试的规则是:除非你 在BVT中加入了相应的测试,否则就不能修正任何错误。这样,每当要修正某个错误的时候,你都必须添加相应的测试,以确保BVT不会再把错误放过去。而 且,这个测试应该引导你去考虑更多的测试、编写更多的测试来加强BVT。


    7. 主创建
          创建过程的自动化对于单个开发者来说很有意义,但是它真正发光的,还是在整个系统的主创建(master build)的生成。我们发现,主创建过程能让整个团队走到一起来,让他们及早发现集成中的问题。
          第一步是要选择运行主创建的机器。我们选择了一台叫做“投石车”的计算机(我们经常玩“帝国时代”J),这是一台装有四个CPU的服务器,非常适合专门用来做创建。(由于完整的创建需要相当长的时间,所以这种马力是必须的。)
          创建进程是在一个随时保持运行的Java类中进行的。如果没有创建任务,创建进程就一直循环等待,每过几分钟去检查一下代码仓库。如果在最后的创建之后没有人归还任何代码,进程就继续等待。如果代码仓库中有了新的代码,就开始创建。
          创建的第一阶段是完全提取仓库中的代码。Starteam已经为我们提供了相当好的Java API,所以切入代码仓库也很容易。守护进程(daemon)会观察五分钟以前的仓库,看最近五分钟里面有没有人归还了代码。如果有,守护进程就会考虑等 五分钟再提取代码(以免在别人归还代码的过程中提取)。
    守护进程将全部代码提取到投石机的一个目录中。提取完成之后,守护进程就会在这个目录里 调用Ant脚本。然后,Ant会接管整个创建过程,对所有源代码做一次完整的创建。Ant脚本会负责整个编译过程,并把得到的class文件放进六个 jar包里,发布到EJB服务器上。
          当Ant完成了编译和发布的工作之后,创建守护进程就会在EJB服务器上开始运行新的jar,同时开始运行BVT测试套件。如果所有的测试都能正常运行通 过,我们就得到了一个成功的创建。然后创建守护进程就会回到Starteam,将所有提取出的源代码标记上创建号。然后,守护进程会观察创建过程中是否还 有人归还了代码。如果有,就再开始一次创建;如果没有,守护进程就回到它的循环中,等待下一次的归还。
          创建结束之后,创建守护进程会给所有向最新一次创建归还了代码的开发者发一个e-mail,汇报创建的情况。如果把创建留在代码归还之后去做,而又不用e-mail向开发者通报创建的情况,我们通常认为这是不好的组织形式。
          守护进程将所有的步骤都写在XML格式的日志文件里面。投石车上会运行一个servlet,允许任何人通过它检查日志,以观察创建的状态。(见图1)
    屏幕上会显示出创建是否正在运行、开始运行的时间。在左边有所有创建的历史记录,成功的、失败的都记录在案。点击其中的某一条记录,就会显示出这次创建的详细信息:编译是否通过、测试的结果、发生了哪些变化……
          我们发现很多开发者都经常看看这个页面,因为它让他们看到项目发展的方向,看到随着人们不断归还代码而发生的变化。有时我们也会在这个页面上放一些其他的项目新闻,但是需要把握好尺度。
          要让开发者能在自己的本地机器上模拟主创建过程,这是很重要的。这样,如果集成错误出现了,开发者可以在自己的机器上研究、调试,而不必真的执行主创建过程。而且,开发者也可以在归还代码之前先在本地执行创建,从而降低了主创建失败的可能性。
          这里有一个比较重要的问题:主创建应该是干净的创建(完全从源代码开始)还是增量创建?增量创建会快得多,但是也增大了引入错误的风险,因为有些部分是没 有编译的。而且我们还有无法重新创建的风险。我们的创建速度相当快(20万行代码约15分钟),所以我们乐于每次都做干净的创建。但是,有些团队喜欢在大 多数时候做增量创建,但是当那些奇怪的问题突然出现时,也经常性地做干净的创建(至少每天一次)。
    图1:运行在投石车上的servlet


    8. 代码回归(Check in)
          使用自动化创建就意味着开发者应该遵循某种节奏来开发软件,最重要的就是他们应该经常集成。我们曾经见过一些组织,他们也做日创建,但是其中的开发者却不 经常归还代码。如果开发者几周才归还一次代码,那么日创建又有什么意义呢?我们遵循的原则是:每个开发者至少每天要归还一次代码。
    在开始新的任务之前,开发者应该首先与配置管理系统同步。也就是说,他们应该首先更新本地机器上的源代码。在旧的代码基础上编写代码,这只会带来麻烦和混乱。
          然后,开发者要随时保持文件的更新。开发者可以在一段任务完成之后将代码集成到整个系统中,也可以在任务的中途集成,但是在集成的时候必须保证所有的测试都能通过。
          集成的第一步是要再次使开发者的本地文件与代码仓库同步。代码仓库中所有新近有改动的文件都要拷贝到开发者的工作目录中来,当文件发生冲突时,配置管理系 统会向开发者提出警告。然后,开发者需要对同步后的工作集进行创建,对这些文件运行BVT,并得到正确的结果。
          现在,开发者可以把新的文件提交到代码仓库中。提交完成之后,开发者就需要等待主创建。如果主创建成功,那么这次归还也是成功的。如果主创建失败了,开发 者可以在本地修改。如果修改很简单,就可以直接提交;如果修改比较复杂,开发者就需要放弃这次修改,重新同步自己的工作目录,然后继续在本地开发、调试, 然后再次提交。
          某些系统强制要求归还进程逐个进行。在这种情况下,系统中会有一个创建令牌,同一时间只有一个开发者能拿到令牌。开发者获取创建令牌,再次同步文件,提交 修改,然后释放令牌。这就确保创建过程中,最多只能有一个开发者在更新代码仓库。不过我们发现,即使没有创建令牌,我们也很少遇到麻烦,所以我们也不用这 种方法。经常会有多个人同时向同一个主创建提交代码的情况,但是这很少造成创建失败,而且这样的错误也很容易修复。
          同时,我们还让开发者自己来决定归还过程中的小心程度。这反映出开发者对集成错误出现几率的评估。如果她觉得很有可能出现集成错误,那么她就会在归还之前 先做一次本地创建;如果她觉得根本不可能出现集成错误,那么她可以直接归还。如果犯了错误,在主创建运行时她立刻就会发现,然后她就必须放弃自己的修改, 找到出错的地方。如果错误很容易发现、很容易修补,那么这种错误也是可以接受的。


    9. 总结
          发展一个制度严密的自动化创建过程对于项目的控制是很重要的。许多软件先贤都这样说,但是我们发现,这样的过程在软件开发领域中仍然罕见。
    关键是要让所有的事情都完全自动化,并且要经常进行集成,这样才能尽快发现错误。然后,人们可以随时修改需要修改的东西,因为他们知道:如果他们做的修改引起了集成错误,那也是很容易发现和修补的。一旦获得了这些利益,你会发现自己再也无法放下它们。

    在前一博客 图解持续集成--纯命令行实现.Net项目每日构建 中介绍了通过命令行进行每日构建,在本文中将介绍支持其流程

    本文中使用到的《Code Build & BVT报告》,可在这里下载 

     

    1. 说明
    1.1. Code Build
    Code Build分为Daily Build和Release Build:

    • Daily Build:为每工作日进行编译,采用的方式为每个工作日16:00通知开发小组签入通过编译的代码,由项目组指定人员获取代码服务器最新的代码进行编译,编译成功后包括代码保存到\BuildDaily 对应版本文件夹,并建立测试路径进行Bug回归等。
    • Release Build:为发布编译,采用的方式为每周五上午10:00通知开发小组签入通过编译的代码,由项目组指定人员获取代码服务器最新的代码进行编译,编译成功后仅保存执行文件到\ReleaseDaily对应版本文件夹,并为发布版本建立测试路径。

    1.2. BVT
    BVT(Build Verify Test)的主要任务是保证软件系统集成编译后能够正常运转,不会出现致命性错误影响系统的正常使用,以致影响到接下来的系统测试工作。BVT工作采用软 件系统每发布一个新版本后执行, 每次BVT之后发布《Code Build & BVT报告》。
    2. Build 版本号
    2.1. Build 版本号规则
    Build 版本号从0001开始流水,完整的版本版本号如:V0.8.0215.0713,表示该版本号为V0.8,build号为0215,每次通过BVT的build版本号加1。
    产品暂定的build版本号为V0.8.0200开始
    2.2. Build 版本号标识(建议加入)
    为清楚地在软件中标识软件的build按本号,需要在软件客户端中标明软件的版本,如在软件帮助菜单->关于子菜单中设定该软件的版本号和build版本号。
    3. 代码编译环境
    3.1. Code Build
    Daily Build由项目组指定人员每工作日下午16:00进行(下班前,防止编译失败找不到对应开发人员),编译环境为测试服务器(IP: 10.169.169.108)环境。
    发布路径 http://IP/PBOC.Web_D_0.8.0200/Login.aspx
    Window 2003 Server、SQL Server2000、Office 2003、IIS6.0、
    .NET Frame1.1 & .NET frame2.0、VS2003/VS2005
    3.2. Release Build
    BVT由项目组指定人员每周五上午10:00进行, 编译环境为测试服务器(IP: 10.169.169.108)环境。
    发布路径 http://IP/PBOC.Web_R_0.8.0205/Login.aspx
    Window 2003 Server、SQL Server2000、Office 2003、IIS6.0、
    .NET Frame1.1 & .NET frame2.0、VS2003/VS2005
    4. 代码管理规范
    4.1. 代码管理
    源代码(Source Code) 在BVT通过后由指定人员每天从VSS获取最新的版本,编译后的可执行代码在BVT通过后由指定人员按如下代码保存目录结构保存源代码(source_code)
    Daily Build   :  D:\BuildDaily\版本号\
    Release Build :  D:\BuildRelease\版本号\
    4.2. 代码保存目录结构                        

    5. BVT 流程
    5.1. 流程图
     BVT测试环境:测试服务器BVT环境 (IP: 10.169.169.108)
     BVT 流程
         

    5.2. Daily Build手工流程

    序号

    步骤

    备注

    1

    每日16:00通知开发人员把程序签入VSS服务器

     

    2

    登陆编译服务器,通过VSS获取最新的程序代码

     

    3

    对最新的程序代码进行编译,记录编译的结果。如果不成功需屏蔽或修改等方式,使代码可正常编译。

     

    4

    进行BVT工作,结果记入《Code Build & BVT报告》

     

    5

    DailyBuild的目录下建立文件夹,命名格式为V0.8.0230.0729,复制编译成功的程序到此文件夹

     

    6

    修改Web.configConfig\PBOC.config中的配置

     

    7

    发布该程序,发布名称格式为PBOC.Web_D_ V0.8.0230.0729

     

    8

    写《Code Build & BVT报告》,签入到VSS

     

    9

    按照邮件模板给成员发布版本

     

     

    5.3. Daily Build自动流程

    序号

    步骤

    备注

    1

    每日16:00通知开发人员把程序签入VSS服务器

     

    2

    启动自动编译脚本

     

    3

    进行BVT工作,结果记入《Code Build & BVT报告》

     

    4

    写《Code Build & BVT报告》,签入到VSS

     

    5

    按照邮件模板给成员发布版本

     

    5.4. Release Build流程

    序号

    步骤

    备注

    1

    周五10:00通知开发人员把程序签入VSS服务器

     

    2

    登陆编译服务器,通过VSS获取最新的程序代码

     

    3

    对最新的程序代码进行编译,记录编译的结果。如果不成功需屏蔽或修改等方式,使代码可正常编译。

     

    4

    进行BVT工作,结果记入《Code Build & BVT报告》

     

    5

    ReleaseBuild的目录下建立文件夹,命名格式为V0.8.0230.0729,复制编译成功的程序到此文件夹

     

    6

    修改Web.configConfig\PBOC.config中的配置

     

    7

    发布该程序,发布名称格式为PBOC.Web_R_ V0.8.0230.0729

     

    8

    写《Code Build & BVT报告》,签入到VSS

     

    9

    按照邮件模板给成员发布版本

     

    5.5. 邮件格式

    1

    收件人

    项目组成员

    2

    抄送人

    相关负责人

    3

    主题

    如:Daily Build&Release Build报告 | ReleaseBuild V0.8.0208.0706

    4

    内容

    报告摘要:

    编译时间:          2007-07-06 10:00

    编译服务器:      10.169.169.74

    型:                Release Build

    版本号:              V0.8.0208.0706

    编译情况:           编译成功,BVT通过

    访问地址:           http://IP/PBOC.Web_R_V0.8.0208.0706/Login.aspx

    说明:版本号为编译版本号,只针对内部人员所有CodeBuild文档参见\\VSS_DB_DJZ /综合报送产品改造项目/测试目录/ CodeBuild报告

     


    6. 配合工作
    6.1. 和开发组的配合

    •  从2007-6-24 下午16:00开始正式进行BVT流程,请开发小组在这之前将各自的源代码Check-in 到VSS服务器。
    • 开发小组必须保证在每天16:00前将自己最新且完成的源代码更新回代码数据库(Check In),如果没有完成那么可以不更新(保持Check Out 状态),但要求保证不将任何会导致编译错误的代码更新(Check In)进代码服务器
    • BVT原则上每天进行,如果不能Build通过或BVT不通过,则开发小组应优先解决该问题,并且确保当日内将该问题解决。
    6.2. 和测试组的配合
    BVT人员除提供给测试小组通过BVT的前/后台代码,同时还负责测试环境执行代码更新,并且有义务协助测试组更新调试更新代码后的测试环境。
    http://www.cnblogs.com/shishanyuan/archive/2011/08/11/2135328.html
  • 相关阅读:
    较全的ASCII码对照表
    关于.NET Framework 3.5 SP1 bootstrapper 包(安装和部署)的解决方案
    C#中DllImport用法和路径问题
    在Winform中给的button等控件添加快捷键的几种方法。
    DataGridView之为每行前面添加序号
    【软件设计过程PowerDesigner v12简介】
    死锁与活锁的区别,死锁与饥饿的区别
    性能优化之 — AS3.0对象池运用
    wc之“HelloWorld”
    php之memcache缓存技术
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2153997.html
Copyright © 2020-2023  润新知