跳到内容

持续集成/持续交付的软件开发

来自维基教科书,开放的书籍,开放的世界

CI是必要的

[编辑 | 编辑源代码]

在软件工程中,持续集成 (CI) 实施了持续的质量控制流程——少量工作,频繁执行。持续集成旨在通过替换完成所有开发后进行质量控制的传统做法,来提高软件质量并缩短交付时间。维基百科:持续集成[1]

持续集成 (CI) 工具不再仅仅是项目开发中“可有可无”的东西。作为一个花费了比我想谈论更多的时间来浏览文档,并确保参考、可追溯性、文档版本和设计输出在设计历史文件 (DHF) 中得到正确记录的人,我希望阐明使用 CI 来自动化这种繁琐且容易出错的人工操作的价值。CI 不应该被视为“可有可无”的东西。相反,它是绝对必要的!

CI 环境提供了 DHF 所需的一切

当我称持续集成是绝对必要的时候,我的意思是 CI 工具和流程都是必不可少的。CI 工具接管了人类试图使大量文档始终可追溯的尝试(有时是微弱的尝试),并迫使计算机系统做它最擅长的工作。使用 CI 工具不仅仅是那些喜欢将其纳入的爱好者的深奥做法。正如你将在本章中学习的那样,持续集成是优秀的开发团队一直尝试的事情,但他们往往未能利用软件工具来简化流程。更进一步,开发团队可以使用 CI 工具来简化他们以前从未梦寐以求的步骤!

持续集成是指持续编译和构建项目树以及持续测试、发布和质量控制。这意味着在整个项目中,在每个阶段,开发团队都将拥有一个包含至少部分文档和测试的构建版本。CI 构建用于在特定时间执行特定任务。一般来说,这意味着构建是在与系统实际生产环境非常接近的环境中执行的。此外,CI 环境应该用于提供关于构建性能、测试、版本控制系统和工单系统的统计反馈。在开发环境中,团队可以使用版本控制工具(例如 Subversion)来链接到工单。这样,任何 CI 构建都将与特定的变更集相关联,从而提供与问题、需求和最终的追踪矩阵的链接。为此,正确使用 CI 环境可以成为 DHF。


开发团队应该尝试频繁地执行持续集成构建,以确保提交和构建之间没有额外的版本控制更新窗口,并且任何错误都可以在开发人员注意到并立即纠正之前出现。这意味着对于正在开发的项目,应该配置一个检查来及时触发构建。同样,开发人员提交代码变更集时,一般来说最好验证他们自己的变更集不会破坏持续集成构建。

请允许我说明一下“构建”这个词。大多数软件工程师认为构建是编译和链接的输出。我建议摆脱这种狭隘的定义并将其扩展。一个“构建”是完成(在编译器意义上和更广泛意义上)所有成功产品交付所必需的方面。CI 工具运行开发团队告诉它运行的任何脚本。因此,团队可以自由地使用 CI 工具作为构建管理器(抱歉,构建经理,我并不是要威胁你的工作)。它可以编译代码,创建安装程序,捆绑任何和所有文档,创建发布说明,运行测试并向团队成员通知其进度。

Jenkins CI

[编辑 | 编辑源代码]

虽然有很多工具,但我将重点介绍最流行的工具之一,Jenkins CI。这是最受欢迎的(开源)工具之一。Jenkins CI(以前被称为 Hudson 的产品的延续)允许通过以下方式进行持续集成构建:

  1. 它与流行的构建工具(ant、maven、make)集成,以便它可以运行适当的构建脚本,在与生产环境非常接近的环境中进行编译、测试和打包。
  2. 它与版本控制工具集成,包括 Subversion,以便可以根据主干中的项目位置设置不同的项目。
  3. 它可以配置为通过时间和/或变更集自动触发构建。(例如,如果在项目的 Subversion 存储库中检测到新的变更集,则会触发新的构建。)
  4. 它报告构建状态。如果构建失败,可以配置它通过电子邮件通知个人。

上面的屏幕截图展示了 Jenkins CI(或任何 CI 工具)的主页可能是什么样子。可以将其配置为允许不同级别的用户登录,并且可以按项目进行设置。此主页列出了当前处于活动状态的所有项目,以及状态(有关构建的一些详细信息)和侧面的某些配置链接。这些链接可能对普通用户不可用。

单击任何项目(“作业”)链接,可以查看更多有关构建历史记录和状态的详细信息。此图像向我们展示了 CI 环境中的概述屏幕可能是什么样子,但真正体现 CI 环境设置良好的打包益处的是,在详细的项目级别。

在第一个问题被提出之前,先回答一下:是的,软件项目应该有持续集成 (CI) 构建。这适用于大型团队的项目,也适用于小型团队的项目。虽然 CI 构建对于大型团队的协作非常有用,但即使是最小的团队也能从中获得巨大的价值。即使是单独工作的软件工程师也能从持续集成构建中获益。

为什么进行持续集成构建

[编辑 | 编辑源代码]

在第一个问题被提出之前,先回答一下:是的,软件项目应该有持续集成 (CI) 构建。这适用于大型团队的项目,也适用于小型团队的项目。虽然 CI 构建对于大型团队的协作非常有用,但即使是最小的团队也能从中获得巨大的价值。即使是单独工作的软件工程师也能从持续集成构建中获益。

构建与变更集的追踪

[编辑 | 编辑源代码]

好吧,我开始重复了,但追踪是一件好事,有了这种设置,我就可以在各个地方进行追踪!有了所有流程到位,从我们的标准操作程序到工作说明、用例和需求、工单到变更集,我们如何知道参与项目的团队成员是否真正按照程序操作?CI 环境,至少在代码的持续开发方面,为所有其他活动提供了一个单一的概览点。从这里,我们可以看到变更集、工单、构建状态和测试覆盖率。使用适当的插件,我们甚至可以深入了解正在开发的代码的质量。

当然,为了实现这一点,我们需要明智地使用工单系统。使用 Redmine 以及几乎所有好的工单系统,我们可以捕获软件需求和软件设计的元素作为父工单。这些父工单有一对多的子工单,子工单本身可以有子工单。父工单只有在子工单完成时才能关闭或标记为完成。

即时反馈

[编辑 | 编辑源代码]

在最基础的层面上,Hudson 只做一件事:它运行我们告诉它运行的任何脚本。Hudson 的强大之处在于我们可以告诉它运行任何我们想运行的脚本,记录结果,保存构建工件,运行第三方评估工具并报告结果。通过 Subversion 集成,Hudson 将显示与特定构建相关的变更集。它可以配置为以我们想要的任何时间间隔(夜间、每小时、每次代码提交时等)生成构建。

就我个人而言,每次我进行任何重要的代码提交时,我都会做的第一件事就是检查 CI 构建是否成功。如果我破坏了构建,我就会着手解决问题(如果我无法快速解决问题,我会回滚我的变更集,以便 CI 构建继续工作,直到我修复了问题)。

Jenkins 可以配置为在构建结果时向团队成员发送电子邮件,并且设置它以便在构建中断时向开发人员发送电子邮件可能很有用。

中央构建位置

[编辑 | 编辑源代码]

一般来说,您的项目的构建工件不应是代码库的一部分(此规则有例外)。构建工件应该放在您的持续集成构建工具中,在那里任何团队成员都可以查看和使用它们。这些工件包括测试结果、编译的库和可执行文件。通常,发布的构建是在某个开发人员的机器上本地创建的。这是一个严重的问题,因为我们无法很好地知道实际用于创建该构建的文件。是否有配置更改?是否引入了错误?是否使用了错误的文件版本?

虽然开发人员有充分的理由在本地生成和测试构建,但用于测试(或更重要的是发布)的正式构建绝不能在本地创建。永远不要。

构建工件未签入源代码控制代码库的原因有很多,但最主要的原因是,我们不想对这些项目构建的环境做出任何假设。构建工件应保留在我们的 CI 环境中,在这里我们知道生成构建的条件。

此外,由于这些构建在 CI 构建环境中易于访问且已标记,任何团队成员都可以轻松访问任何给定的构建。特别是,可能需要使用特定构建来重新创建已发布用于内部或外部使用的构建中的问题。由于我们知道构建的标签(赋予它的版本号)以及构建的代码库变更集号(因为我们的构建和安装脚本包含它),我们准确地知道从 CI 构建服务器中提取哪个构建以重新创建必要的条件。

4. 单元测试反复运行(反复运行)

开发人员应尽一切努力防止 CI 构建中断。当然,这并不总是有效。我由于以下原因无数次地破坏了 CI 构建:- 我忘记添加必要的库或新文件 - 我忘记提交配置更改 - 我不小心将更改包含在我的变更集中 - 它在本地工作但在 CI 构建服务器上构建时失败(这就是 CI 构建服务器应尽可能模仿生产环境的原因) - 单元测试在本地工作但在 CI 构建服务器上失败,因为存在一些环境差异

如果没有带有良好单元测试的 CI 构建环境,此类问题只会在此后被发现,或者被其他沮丧的团队成员发现。在这方面,CI 构建让我们免受许多头痛。

最难修复(更不用说找到)的软件缺陷是不一致发生的缺陷。数据库锁定问题、内存问题和竞争条件会导致此类缺陷。这些缺陷很严重,但如果我们从未发现它们,我们如何修复它们呢?

最好拥有超越我们传统上认为的“单元测试”的单元测试,并可能进一步采取一些步骤,例如自动化功能测试。这是团队成员经常(错误地)觉得没有足够的时间完成所有工作的另一个领域。

随着 Hudson 每次执行构建时运行所有这些测试,我们有时会遇到测试突然无故失败的情况。它以前工作过,一小时前工作过,并且代码没有发生任何变化,那么这次它为什么失败了呢?请注意,这种情况会发生。

5. 与其他方便的功能集成,例如 Findbugs、PMD 和 Cobertura

不赘述,有很多很棒的工具可以与 Hudson 一起使用来评估代码是否存在潜在的错误、糟糕的编码实践和测试覆盖率。这些工具确实很有用。使用它们。

发布软件

[编辑 | 编辑源代码]

发布软件时,通常会赋予其某种版本号(例如,1.0)。这很好,但它没有告诉我们构建的具体内容。最好在发行版中包含 Subversion 变更集号,以便我们始终准确地知道构建包含什么。我将在某个地方包含一个 build.xml(或 build.prop)文件,其中包含发行版的版本号、Subversion 变更集号和构建日期。对于最后两个值,这些值可以(也应该)由您的构建脚本自动生成。

至于在 Linux/Unix 中实际使用 Subversion,所有命令都可以在命令行中使用。在 Windows 中工作时,我真的很喜欢使用 TortoiseSVN。它与 Windows 文件资源管理器集成,显示图标以指示任何版本控制文件的狀態。它还提供了一个很好的界面来查看文件差异(甚至查看 Word 文档的差异)和代码库历史记录。

票据和问题跟踪

[编辑 | 编辑源代码]

长期以来,我们一直将我们的票据系统视为“错误跟踪器”。以前最流行的开源工具之一 Bugzilla 甚至在其名称中使用了“bug”一词。但问题跟踪并不意味着它只对跟踪软件缺陷有用。恰恰相反!它可以用于软件设计和开发中的所有事情,从解决文档需求到捕获软件需求,再到处理软件缺陷报告。

关于这一点,我想补充一点可能需要整篇文章才能说明。我认为最好避免使用标准文档来捕获软件用例、需求、危害等。通过在我们的问题跟踪工具中捕获与软件项目相关的所有内容,我们可以利用 Trac 或 Redmine 等工具的功能来增强团队协作和项目跟踪。但我现在不会贪多嚼不烂。

当我第一次开始将我所有的想法写下来时,我打算使用 Trac 作为我的示例 (http://trac.edgewall.org/)。Trac 是一个很棒的工具,但现在有更好的工具,那就是 Redmine (http://www.redmine.org/)。

Trac 的主要缺陷在于它并不适合(根本不适合)处理多个项目。一个 Trac 安装只能与一个 Subversion 代码库集成,并且票据系统只能处理一个项目。我仍然喜欢 Trac 用于将票据分组到冲刺中的方式,但在 Redmine 中使用子项目,可以实现类似的分组。过去,如果使用任何工具,我们使用 Bugzilla 或 Clearquest 来处理问题跟踪。这些工具在当时非常出色,但它们与其他工具的集成度不高,也不包含维基、日历或甘特图等功能。(诚然,我已经很多年没有使用 Clearquest 了,所以我真的不知道它是否已经解决了其中的一些需求。)在本地工作但在 CI 构建服务器上失败,因为存在一些环境差异

如果没有带有良好单元测试的 CI 构建环境,此类问题只会在此后被发现,或者被其他沮丧的团队成员发现。在这方面,CI 构建让我们免受许多头痛。最难修复(更不用说找到)的软件缺陷是不一致发生的缺陷。数据库锁定问题、内存问题和竞争条件会导致此类缺陷。这些缺陷很严重,但如果我们从未发现它们,我们如何修复它们呢?

最好拥有超越我们传统上认为的“单元测试”的单元测试,并可能进一步采取一些步骤,例如自动化功能测试。这是团队成员经常(错误地)觉得没有足够的时间完成所有工作的另一个领域。

随着 Hudson 每次执行构建时运行所有这些测试,我们有时会遇到测试突然无故失败的情况。它以前工作过,一小时前工作过,并且代码没有发生任何变化,那么这次它为什么失败了呢?请注意,这种情况会发生。

5. 与其他方便的功能集成,例如 Findbugs、PMD 和 Cobertura

不赘述,有很多很棒的工具可以与 Hudson 一起使用来评估代码是否存在潜在的错误、糟糕的编码实践和测试覆盖率。这些工具确实很有用。使用它们。

那么 Redmine 有什么好的地方呢?

  1. 维基的力量:您的文档包含所有项目管理详细信息、工作说明、用例、需求等。再次说明,我认为这些信息可以放在维基中,但对于有些人来说,这可能是他们不习惯的一步。
  2. 也就是说,所有开发人员设置、经验教训和其他非正式笔记都可以放在维基中。有一次,我花了将近 4 天的时间来跟踪一个非常奇怪的缺陷。当我终于弄清楚所有事情时,我了解了很多关于其他人肯定会遇到的一个非常奇怪的问题。我创建了一个维基页面来解释这个问题。
  3. 维基的另一个强大功能是,在 Redmine(和 Trac)中,我们不仅可以链接到其他维基页面,还可以链接到票据(问题)、项目、子项目和 Subversion 变更集。再次说明,更多跟踪。很好。
  4. Subversion 集成:通过集成 Subversion 和 Redmine,我可以在这两者之间相互链接。这些工作说明应该向团队解释我们如何使用这些流程,其中包括任何工单只有在链接到 Subversion 的变更集后才能关闭(除非工单被拒绝)。Redmine 可以配置为在 Subversion 变更集提交中搜索关键字。例如,如果我正在检入几个解决问题 #501 的文件,我可能会添加类似这样的评论:“修正了如此这般,解决了问题 #501。”我们可以配置 Redmine 来寻找“fixes”这个词并使用它。Redmine 可以将这个词作为标记来关闭工单,并链接到我提交变更集时创建的变更集。同样,当我们查看 Subversion 历史记录时,我们会看到“#501”附加到变更集,并链接到相应的工单。跟踪双向工作... 太棒了!
  5. 多项目处理(以及与不同 Subversion 存储库的集成):这是我(以及其他人)转向 Redmine 的主要原因。Trac 很好,但它只能处理单个项目。Redmine 可以处理多个项目,可以在整个公司范围内用于所有开发工作,并且每个项目可以绑定到不同的 Subversion 存储库。此外,单个项目可以拥有多个子项目。这使我们能够灵活地使用子项目来处理冲刺、特定分支版本等等。
  6. Hudson 集成:通过集成 Hudson,我无需离开 wiki 就可以查看我的 CI 构建情况。不仅如此,我还可以从 wiki 或工单系统中的任何页面链接到特定的 CI 构建。
  7. 完全可配置性:Redmine 中的所有内容都可以配置。是的,所有!我们甚至可以配置工单的流程。

问题跟踪系统

[编辑 | 编辑源代码]

我建议仅仅将功能需求留在软件需求规格说明书中是不够的。这无法提供足够的跟踪,也无法提供从想法到功能代码的清晰路径。以下是我建议的步骤。

  1. 所有需求和软件设计项都作为工单输入。目前,它们只是没有“子工单”的高级工单。
  2. 开发团队在首席开发人员的组织下,将每个高级工单分解成尽可能多的子工单。使用工单系统,我们建立关系,以便父工单(即需求本身)只有在所有子工单完成时才能关闭。(注意:可能需要为每个工单要求相应的单元测试。)
  3. 风险(我不会在这篇文章中解释风险和风险分析)通过文档、需求和测试的组合来缓解。我们可以利用工单系统来记录我们的风险,并像需求一样提供跟踪。这并不能消除对可追溯性矩阵的需求,但它确实增强了我们创建和维护它的能力。(附带说明,我认为使用 Redmine wiki 来记录用例、需求、风险分析、软件设计文档和可追溯性矩阵会很棒,从而允许在内部链接,但这可能难以推行。)
  4. 并非所有需求都是功能代码需求。许多是文档和/或质量需求。这些需求应该在同一个工单系统中记录。使用系统标记工单类型的能力来处理不同类别的事实。通过这样做,即使是文档需求也可以在我们系统中进行跟踪。
  5. 我不是说工单在这么早的阶段就会被锁定。绝不会!工单在整个项目设计和开发过程中被创建、关闭和修改。我们的项目计划(在开始编写代码之前创建)向我们解释哪些工单需要在何时完成,重点关注最高级的工单。也就是说,我发现最好使用某种迭代方法(并允许开发团队使用子迭代或“冲刺”。)

参考资料

[编辑 | 编辑源代码]
  1. "持续集成(维基百科)". 检索于 2011-08-29.
华夏公益教科书