CI/CD 的自动化测试

自动化测试是持续集成和部署的一大基石。 通过在 DevOps 管道中构建持续测试,您可以显著提高软件质量。

持续集成和部署 (CI/CD) 就是要频繁地交付增量更改,以便定期获得有关产品或服务的反馈。 不过,更快、更频繁地交付不应降低产品的质量。 毕竟,无论用户多么渴望亮眼的新功能,稳定且能够正常运行的软件都是最低标准。

因此,对于 CI/CD 做法而言,可靠而彻底的自动化测试流程对打造值得信赖的最新构建至关重要。

为什么需要自动执行 CI/CD 测试?

测试是确保软件质量的关键,早已成为软件开发做法的一部分。

在瀑布式环境中,手动测试或 QA 阶段在代码开发和集成之后进行。

💡瀑布式策略通常被称为瀑布模型,这是一种线性和顺序式软件开发和项目管理方式。 它是最早用于软件开发的模型之一,特点是包含不同阶段的结构化和系统化流程。

这种做法的一个弊端是,在编写代码很久之后才能检查代码是否按预期运行。 到那时,可能已经在那些代码的基础上构建了更多代码,使得修正任何问题变得更加困难。 另外,这也会减慢新功能和错误修正的交付速度。

相比之下,敏捷方式更倾向于短的迭代式开发周期,这样可以更频繁地发布产品,获得用户的反馈,并针对下一步构建的内容做出明智的决定。 CI/CD 通过自动执行从编写代码到向用户发布代码之间的各个步骤来支持这种工作方式,使整个流程更加可靠和高效。

要想成功采用这种 DevOps 方法,您需要定期提交、构建和测试代码更改,最好每天进行多次。 不过,即使在一个小团队中,每天执行一次或多次整套手动测试也不现实。 正因为如此,自动化测试是任何 CI/CD 管道的重要组成部分。

尽管编写自动化测试需要预先投入时间,但这项工作很快就会得到回报,特别是当您开始更频繁地提交和部署更改时。 投资自动化测试可以带来几大好处,包括:

  • 对每次代码更改进行检查,以确保代码按预期工作并且没有引入新 bug。
  • 与手动执行同等测试相比,反馈交付的速度更快。
  • 在 bug 出现后立即予以修正往往会更有效率,因为这些更改在您的脑海中记忆犹新,并且尚未在它们之上构建任何其他内容。
  • 测试结果比手动测试更可靠,因为要求任何人重复执行相同的任务,难免会出现错误和不一致的情况。
  • 自动化测试可以并行运行,因此在时间紧迫的情况下,您可以(在基础架构允许的范围内)扩大质量保证的规模。

虽然测试自动化省去了许多枯燥、重复性任务,但这并不会让 QA 工程团队无事可做。 除了定义相关用例并确定其优先级外,QA 团队还应参与编写自动化测试,通常是与开发者合作。 无法自动化的测试部分也需要 QA 工程师,这一点我们将在后面讨论。

测试在 CI/CD 流程的什么位置?

自动化测试在整个管道的多个阶段进行。

CI/CD 和 QA 自动化的核心是实现紧密的反馈循环,以便让团队尽早发现问题。

在问题出现后尽快修正要容易许多,因为这有助于避免在不良代码基础之上编写更多代码。 对于您的团队来说,在他们继续做下一件事并忘记上下文之前进行更改也更有效率。

许多自动化构建测试工具都支持 CI/CD 工具集成,因此您可以将测试数据馈送到管道,分阶段运行测试,并在每一步后提供结果。 基于您的 CI 工具,您还可以根据上一步的测试结果选择是否将构建移至下一阶段。

为了通过自动化测试充分利用管道,通常需要对测试排序,优先运行最快的构建测试。 这能让您更快地得到反馈,并更有效地使用测试环境,因为您可以确保在运行更长、更复杂的测试之前,初始测试已经通过。

关于如何优先创建和运行自动化测试,可以从测试金字塔的角度来考虑。

构建测试金字塔

测试金字塔是在 CI/CD 管道中根据测试的相对数量和执行顺序确定自动化测试优先级的工具。

测试金字塔最初由 Mike Cohn 定义,底部为单元测试,中间为服务测试,顶部为 UI 测试。

测试金字塔包括以下步骤:

  • 从坚实的基础开始,即快速且易于运行的自动化单元测试。
  • 然后,继续编写更复杂、运行时间更长的测试。
  • 最后进行少量最复杂的检查。

您应该考虑哪种类型的 CI/CD 测试? 我们来了解一下各个选项。

单元测试

单元测试构成了测试金字塔的基础。 这些测试旨在处理尽可能小的行为单元,确保代码按预期运行。 例如,如果您正在构建一款天气应用,而将值从摄氏度转换为华氏度可能是某个更大功能的一部分。 您可以使用单元测试来检查温度转换函数是否能针对一系列值返回预期结果。 每次更改相关代码段时,您都可以使用这些单元测试来确认此特定方面是否按预期工作,而无需每次都构建和运行整个应用。

对于决定投资编写单元测试的团队来说,开发者通常会在编写相关代码时负责添加单元测试。 测试驱动型开发 (TDD) 体现了这一流程(我们将在下文讨论),但 TDD 并不是编写单元测试的必要条件。

另一种方式是将单元测试作为每项开发任务已完成定义的一部分,并在执行代码审查或使用代码覆盖率报告时验证这些测试是否到位。

如果您使用的是现有系统,并且之前没有在单元测试上进行任何投入,那么从头开始为整个代码库编写单元测试可能会让人觉得是一个难以逾越的障碍。 虽然使用单元测试实现广泛覆盖是推荐做法,但是您仍然可以从已有的基础开始,逐步进行扩充。

如果您的代码目前没有良好的单元测试覆盖率,请考虑通过与团队建立约定的方式,逐步为接触到的每一段代码添加相应的单元测试。 这种策略可以确保覆盖所有新代码,并优先处理您交互最多的现有代码。

集成测试

通过集成测试,您可以确保软件多个部分之间的交互,如应用程序代码与数据库之间的交互或对 API 的调用,都能按预期运行。

可以将集成测试分为广义和狭义。 采用狭义方法时,会使用测试替身而非实际模块来测试与其他模块的交互。 广义集成测试使用实际组件或服务。 回到天气应用的示例,广义集成测试可能会从 API 获取预测数据,而狭义测试则会使用模拟数据。

根据项目的复杂程度以及所涉及的内部和外部服务的数量,您可能需要从一层狭义集成测试开始。 与广义集成测试相比,这些测试运行得更快(并能提供更快的反馈),因为它们不需要系统的其他部分可用。

如果狭义测试成功完成,就可以运行一组广义集成测试。 由于这些测试运行时间较长且维护开销较高,最好将其限制在产品或服务中优先级较高的领域。

端到端测试

端到端测试也称全栈测试,着眼于整个应用程序。 它们通常用于验证业务用例,例如用户是否可以创建帐户或完成事务。

虽然自动化端到端测试可以通过 GUI 运行,但不一定要这样做;API 调用也可以测试系统的多个部分(尽管 API 也可以通过集成测试进行检查)。

测试金字塔建议减少这些测试的数量,不仅是因为它们需要更长的运行时间,还因为它们往往较为脆弱。 用户界面的任何更改都可能破坏这些测试,导致构建测试结果中出现无用的噪声,并需要额外的时间来更新测试。 因此,仔细设计端到端测试,并了解低级测试已经覆盖的内容,才能充分发挥其作用。

行为驱动型开发

行为驱动型开发 (BDD) 是一种协作式软件开发方式,旨在消除开发者、测试人员和业务利益相关者之间的沟通障碍。 它是 TDD 的扩展,强调软件的行为而非其实现 。

BDD 可以为开发集成测试和端到端测试提供有用的策略。 它的一些关键方面包括:

  • 关注行为:BDD 侧重于通过平白语言的示例来指定系统的具体行为。 这些示例通常以 Given-When-Then 的格式来描述初始状态、行为和预期结果。
  • 协作:BDD 鼓励开发者、测试人员和业务利益相关者之间的协作,以确保对需求有共同的理解。 此协作流程有助于澄清期望并减少误解。
  • 可执行的规范:BDD 的场景以一种可以作为测试自动执行的方式编写。 Cucumber、SpecFlow 和 JBehave 等工具通常用于以人类可读的格式编写这些场景,并将其链接到自动化测试。
  • 文档:这些场景既作为文档也作为测试,为系统行为提供了清晰、生动的规范。 这使理解系统及其要求变得更加容易。
  • 迭代开发:BDD 通过鼓励持续反馈和改进来支持迭代和增量开发。 每次迭代都涉及编写和实现新的场景,确保软件的发展与业务需求保持一致。

性能测试

尽管测试金字塔未涉及性能测试,但其值得考虑,特别是对于将稳定性和速度列为关键要求的产品。

性能测试领域具有一系列测试策略,旨在检查软件在实际环境中的表现:

  • 负载测试用于检查系统在需求增加时的表现。
  • 压力测试会有意超出预期的使用量。
  • 浸泡(或持久)测试用于衡量持续高负载下的性能。

这些类型的测试的目的不仅仅是确认软件能否处理定义的参数,还要测试在超出这些参数的限度时的表现。理想情况是优雅地结束,而不是立即崩溃。

测试环境

性能测试和端到端测试都需要与生产非常相似的环境,还可能需要构建测试数据。 为了使自动化测试机制能够提供可靠性,每次测试都必须以相同的方式运行。 这意味着要确保测试环境在每一次运行之间保持一致,并在生产环境中部署更改时更新测试环境以匹配生产环境。

手动管理这些环境非常耗时。 对于每个新构建,自动执行创建和拆除预生产环境的流程既能节省时间,又能确保自动化测试机制的一致性和可靠性。

测试驱动型开发如何支持自动化测试?

测试驱动型开发 (TDD) 是一种源于极限编程 (XP) 的开发方式。 采用 TDD,第一步是为要添加的功能编写测试用例列表。 接着,您一次处理一个测试用例,先编写(失败的)测试用例,然后再编写使测试通过的代码。 最后,在处理下一个测试用例之前,根据需要重构代码。 此流程可以概括为“Red, green, refactor”(红绿重构)或“Make it work; make it right”(使其工作;使其正确)。

TDD 的一个主要优点是,它会迫使您为编写的任何新代码添加自动化测试。 这意味着您的测试覆盖率会不停增长,每次更改代码时都能获得快速、定期的反馈。 测试驱动型开发的其他好处包括:

  • 促进迭代方式。 确定测试列表后,可以每次处理一个测试用例,同时完善剩余的测试列表。
  • 促使您将接口与实现分离。 遵循“red, green, refactor”(红绿重构)工作流是其中的核心。
  • 在工作过程中获得即时反馈。 您不仅要验证最新的更改是否通过了测试,还要检查现在是否有其他测试失败。
  • 由于测试明确了意图,可以维持记录良好的代码。 这反过来又会使您的代码更易于维护,并且能够加快新团队成员的上手速度。

TDD 是逐步提高自动化测试覆盖率以支持 CI/CD 流程的有效方式。 尽管如此,TDD 并不是有效 DevOps 策略的必要条件。您也可以在没有 TDD 的情况下保持高水平的自动化测试覆盖率。

处理反馈

作为 CI/CD 做法的一部分,运行自动化测试的目的是获得关于您刚刚做出的更改的快速反馈。 倾听和回应反馈是该流程的重要组成部分。 以下最佳做法将帮助您充分利用自动化测试机制:

  • 使您的测试结果易于访问。 CI 服务器通常与自动化测试工具集成,因此您可以在仪表板或信息看板中查看所有结果。
  • 将您的测试报告与消息传递平台(如 Slack)集成,以便收到关于测试结果的自动化通知。
  • 当测试失败时,应尽快找出原因,以便在其基础上构建更多代码之前予以修正。 CI 工具可以识别测试相关的代码库区域,并提供对测试所产生的任何数据(如堆栈跟踪、输出值和屏幕截图)的访问权限,从而加快此流程的速度。
  • 将每个测试设计为只检查一项内容,并准确标记测试,以便能够轻松了解哪里出现了问题。

与以往一样,工具和实践只是方案的一部分。 一个真正优秀的 CI/CD 自动化做法需要一种团队文化,这种文化不仅要认可自动化 CI/CD 测试的价值,还要认识到快速响应失败测试的重要性,使软件保持可部署状态。

持续测试与自动化测试

对于许多团队而言,自动化测试的起点是一个可以手动触发或作为简单持续集成管道一部分的单元测试套件。 随着 DevOps 文化的成熟,您可以通过添加集成测试、端到端测试、安全测试、性能测试等,开始向测试金字塔的顶端迈进。

持续测试指的是将全套自动化测试作为 CI/CD 管道的一部分运行的做法。 在持续测试中,每组代码更改都会自动经过一系列自动化测试,以便尽快发现任何 bug。

持续测试流程的早期阶段可以包括提交更改之前在 IDE 中运行的测试。 对于后期阶段的测试,持续测试通常需要作为管道一部分自动刷新的测试环境。

完全自动化的持续测试流程能够在加快发布速度的同时,尽可能提高对您的代码更改的信心。 通过对软件采用严格的测试机制,您可以显著降低出现 bug 的风险。 自动且持续地运行该流程不仅可以帮助您更高效地工作,还可以让您快速、可靠地部署紧急修正。

虽然持续测试的实施需要时间,但随着 CI/CD 管道其他方面的自动化和测试覆盖率的不断提高,您可以逐步实现这一目标。

CI/CD 和自动化测试是否标志着手动测试的终结?

刚接触 CI/CD 的人可能会有一个常见误区,认为自动化测试会消除对手动测试和专业 QA 工程师的需求。

虽然 CI/CD 自动化为 QA 团队成员腾出了一些时间,但并没有使他们成为多余的人。 QA 工程师不必再将时间花费在重复性任务上,而是可以专注于定义测试用例、编写自动化测试,并运用他们的创造力和聪明才智进行探索性测试。

与精心编写脚本供计算机执行的自动化构建测试不同,探索性测试的要求较为宽松。 探索性测试的价值在于发现那些计划性和结构性测试方式可能遗漏的问题。 基本上,您要寻找的是尚未考虑过和尚未编写过测试用例的问题。

在决定探索领域时,既要考虑新功能,也要考虑如果在生产中出现问题会造成最大危害的系统领域。 为了有效利用测试人员的时间,只有在所有自动化测试都通过后才应进行手动测试。

探索性测试不应该流于手动的重复性测试。 其中的目的不是每次都进行相同的检查。 在探索性测试中发现问题时,您既需要修正这些问题,又需要编写一个或多个自动化测试。 这样一来,如果问题再次出现,就能更早地发现。

测试自动化的持续改进

构建测试套件并不是一件一劳永逸的事情。 自动化测试需要持续维护,以确保它们始终相关并有用。 就像您持续改进代码一样,也需要持续改进测试。

持续为新功能添加自动化测试,并将探索性测试的结果纳入其中,将使您的测试套件保持有效和高效。 此外,还值得花时间了解测试的执行情况,以及是否可以通过重新排序或拆分测试流程的某些部分来更快地提供一些反馈。

CI 工具可以提供各种指标来帮助您优化管道,而不稳定测试指示器可以标记出不可靠的测试,避免让您产生错误的信心或担忧。

但是,尽管指标可以帮助您改善自动化测试流程,您应该避免陷入认为测试覆盖率本身就是目标的误区。 真正的目标是定期向您的用户提供行之有效的软件。 自动化通过提供快速、可靠的反馈来实现这一目标,并在将软件部署到生产环境之前给予您信心。

关于 CI/CD 自动化测试的总结

测试自动化在任何 CI/CD 管道中都扮演着核心角色。 自动运行测试可对代码更改提供快速可靠的反馈。 反过来,这也会提高开发效率,因为尽早识别出 bug 将使它们更容易修正。

根据运行所需的时间对自动化测试进行排序是很好的做法。 应该首先运行单元测试,因为这些测试能够提供最快的反馈,然后运行集成测试,最后运行端到端测试。 如果您没有任何自动化测试,单元测试是最好的起点。 测试驱动型开发 (TDD) 是一种行之有效的开发做法,可以帮助您改进和维持单元测试覆盖率。

随着 DevOps 文化的成熟,您可能希望转向持续测试。 这种转变的一部分将涉及自动执行测试环境的创建和维护。 在编写更高级别的自动化测试时,应考虑优先处理那些风险最大的领域。 这可能需要进行自动化性能测试,如负载测试、压力测试或浸泡测试。 手动探索性测试是一种很好的方式,可以帮助您识别测试覆盖范围中的空白,从而持续改进您的 CI/CD 流程。

TeamCity 如何提供帮助

TeamCity 提供了对测试框架的广泛支持以及一系列测试自动化功能,可以帮助您充分发挥 CI/CD 流程的优势。

速度和可靠性对于有效的自动化测试至关重要,而 TeamCity 在这两方面都进行了优化。 除了提供详细的测试报告以帮助您快速找到问题的根源外,TeamCity 还会自动高亮显示不稳定测试,因此您可以确保只有有效的故障才会被标记。 智能测试重新排序和并行化功能可以更快地提供结果,而远程运行功能则可以在提交之前提供反馈。

因为 TeamCity 提供了与问题跟踪器、IDE、Slack 以及其他平台的集成,无论您在那里工作,都可以收到关于失败测试的通知。 最后,对虚拟机和 Docker 容器的完全支持使您能够自动执行测试环境的管理,并将持续测试作为 CI/CD 流程的一部分来实现。