Python 高手编程系列七十一:持续的开发过程

Python 高手编程系列七十一:持续的开发过程 有很多过程可以大大简化你的开发并减少应用程序从准备到发布或部署到生产环境中的时间。它们的名字中往往包含持续continuous二字我们将在本节讨论最重要和最受欢迎的一个。重要的是要强调它们是严格的技术过程所以它们几乎与项目管理技术无关虽然它们可以非常接近后者。我们将提到的最重要的过程如下。● 持续集成continuous integration。● 持续交付continuous delivery。● 持续发布continuous deployment。列出的顺序很重要因为它们中的每一个都是前一个的扩展。持续部署可以被简单地理解为持续交付的变种。我们将单独讨论它们因为对于一个组织来说即使微小的差别在其他组织可能是至关重要的。事实上这些技术过程意味着它们的实现严格依赖于正确工具的使用。每个过程背后的想法是相当简单的所以你可以建立自己的持续集成/交付/部署工具但最好的方法是选择已经建成的工具。这样你可以更专注于构建你的产品而不是浪费在持续开发的工具链上。持续集成持续集成通常缩写为 CI是一个从自动化测试和版本控制系统中受益的过程它提供全自动集成环境。它可以与集中式版本控制系统一起使用但实际上只有在使用良好的 DVCS 工具管理代码时它才会更好地发挥作用。创建一个仓库是实现持续集成的第一步这是出自极限编程eXtreme ProgrammingXP中一则软件实践。这些原则在维基百科中有清楚的描述并定义了一种确保软件易于构建、测试和交付的方法。实现持续集成的第一个并且最重要的要求是拥有一个完全自动化的工作流程该流程可以测试整个给定修订的应用程序以确定它是否在技术上是正确的。技术上正确的意思是它是没有已知的错误所有的功能可以按预期正常工作。CI 背后的大体思想是测试应该总是在合并到主开发分支之前运行。这只能通过开发团队中的正式安排来处理但实践表明这不是一个可靠的方法。问题是作为程序员我们倾向于过度自信无法批判性地看待我们的代码。如果持续集成仅仅建立在团队安排上它将不可避免地失败因为一些开发人员最终会跳过测试阶段并向可能向始终应保持稳定的主开发分支提交可能出错的代码。而且在现实中即使简单的改变也能引入严重的问题。常见的解决方案是使用专用的构建服务器每当代码库发生更改时就自动运行所有必需的应用程序测试。有许多工具可以简化此过程并且可以轻松地与版本控制托管服务如 GitHub 或 Bitbucket和自建服务如 GitLab集成。使用这样的工具的好处是开发者可以仅在本地运行所选择的测试子集取决于他与他当前的工作相关并且为构建服务器留下潜在的整套集成测试的耗时。这真的加速了开发并且降低了新特性将破坏主代码分支中现有稳定代码的风险。使用专用构建服务器的另一个优点是测试可以在更接近生产的环境中运行。开发人员还应该尽可能地使用与生产相匹配的环境并且有很好的工具例如 Vagrant但是很难在任何组织中实施这一点。你可以在一个专用的构建服务器上或者甚至在构建服务器集群上轻松地完成。许多 CI 工具通过利用各种虚拟化工具来减少问题这些工具有助于确保测试始终在相同且全新的测试环境中运行。如果你创建必须以二进制形式把桌面或移动应用程序分发给用户那么拥有构建服务器也是必须的。显然可以在同一个环境中总是执行同样的构建过程。几乎每个 CI 系统都考虑到在测试/构建完成后应用程序通常需要以二进制形式下载的事实。这种构建结果通常被称为构件build artifacts。因为 CI 工具起源于大多数应用程序是用编译型语言编写的时候所以它们大多使用术语“构建”来描述它们的主要活动。对于诸如 C 或 C 之类的语言这是显而易见的因为如果不构建编译应用程序不能运行和测试。对于 Python这样做没有一点意义因为大多数程序以源形式分发并且可以在没有任何额外构建步骤的情况下运行。因此在我们的语言范围内构建和测试术语在谈论持续集成时通常可以互换使用。测试每一个提交持续集成的最佳方法是在推送到中央仓库的每个更改上执行整个测试套件。即使一个程序员在一个分支中推送了一系列的多个提交通常也有必要分别测试每个更改。如果你决定只在一个版本库推送中测试最新的变更集那么很难找到从中引入问题的回归问题的来源。当然许多 DVCS如 Git 或 Mercurial允许你通过提供等分历史修改记录的命令来限定搜索回归源的时间但在实践中作为连续集成过程的一部分自动执行这些命令会更加方便。当然有一个问题项目有个非常耗时的测试套件可能需要几十分钟甚至几个小时才能完成。在给定的时间段内一个服务器可能无法对每个提交执行所有构建。这将使等待结果更长。事实上长时间运行测试本身就是一个问题这将在后面的“问题 2—过长的构建时间”部分中描述。现在你应该知道你应该总是努力测试每个推送到仓库的提交。如果你没有权限在单个服务器上执行此操作请设置整个构建集群。如果你使用付费服务则需要为更多的并行构建支付更高的价格。硬件比较便宜但是你的开发人员的时间却不是。最终通过更快的并行构建和更昂贵的 CI 计划比你跳过选择更改的测试你可以节省更多的费用。使用 CI 测试合并现实中情况通常很复杂。如果特性分支上的代码通过所有测试但并不意味着在合并到稳定的主流分支时构建不会失败。在 Git 工作流和 GitHub 工作流部分中提到的流行分支策略都假设合并到主分支的代码总是被测试和部署。但是如果你还没有执行合并你怎么能确保满足这个假设由于它比较强调发布分支这是 Git 工作流中一个次要的问题如果良好地实施并精确地使用。但是对于简单的 GitHub 工作流来说这是一个真正的问题合并到 master 通常与冲突相关并且很可能在测试中引入回归。即使对于 Git 工作流这是一个严重的问题。这是一个复杂的分支模型所以可以肯定的事是人们会在使用它时犯错误。所以如果你不采取特殊的预防措施你永远不能确保主代码在合并后通过测试。这个问题的解决方案之一是把合并特性分支到的稳定主分支的责任委托给 CI 系统。在许多 CI 工具中你可以轻松地按需设置构建作业这个作业可以在本地将特定的特性分支合并到稳定分支并将其推送到中央仓库仅当它通过所有测试时。如果构建失败则这样的合并将被恢复使得稳定分支不被触及。当然这种方法在快节奏项目中变得更加复杂其中许多特性分支是同时开发的因为存在不能由任何 CI 系统自动解决的高冲突风险。当然这个问题也有解决方案例如 Git 中的变基rebase。如果你正在考虑进一步实施持续交付过程这种将任何内容合并到版本控制系统中的稳定分支的方法实际上是必须的。如果你的工作流中有一个严格的规则证明稳定分支中的所有内容都是可发布的那么也需要这样做。矩阵测试如果你的代码需要在不同的环境中测试矩阵测试matrix testing是一个非常有用的工具。根据你的项目需求CI 解决方案中可能或多或少地需要支持这样的功能。解释矩阵测试的最简单的方法是以一些开源的 Python 包为例。例如Django 是一个对Python 语言版本的支持集合有着对严格规定的项目。1.9.3 版本列出了 Python 2.7Python 3.4和 Python 3.5这是运行 Django 代码所必须的。这意味着每当 Django 核心开发人员对项目进行更改时必须在这 3 个 Python 版本上执行完整的测试套件以支持此声明。如果单个测试在一个环境中失败整个构建必须标记为失败因为向后兼容性约束可能被破坏。对于这种简单的情况你不需要 CI 的任何支持。有一个强大的工具 Tox参考https://tox.readthedocs.org/除了其他功能还允许你在不同 Python 版本的独立虚拟环境中轻松地运行测试套件。这个实用程序也可以很容易地用于本地开发。但这只是最简单的例子。通常情况下必须在完全不同参数的多个环境中测试应用程序。举几个例子如下。● 不同操作系统。● 不同数据库。● 不同版本的支持服务。● 不同类型的文件系统。全套组合形成一个多维环境参数的矩阵这就是为什么这种设置被称为矩阵测试。当你需要这样的深度测试工作流时很可能需要一些集成支持以便在你的 CI 解决方案中进行矩阵测试。使用大量可能的组合你还需要一个高度可并行化的构建过程因为在每个矩阵上的运行都需要在构建服务器进行大量的工作。在某些情况下如果你的测试矩阵有太多的维度你将被迫做一些权衡。