自动化测试全解析:从基础到实践
1. 故事流程与测试基础
在许多 IT 组织中,常常会构建复杂、繁琐的流程来管理工作流。即便采用敏捷方法的团队,也会积累一系列会议、启动会、评审会、展示会和签字确认流程。然而,讨论一个小的日常任务所花费的时间,往往比实际执行、检查和修复工作的时间还要多。
因此,将事情简化为真正有价值的部分是很有帮助的。对于尚未明确的工作,以下活动最为重要:
1.
明确工作内容
:对要完成的工作有清晰的陈述,在某些情况下,一句话即可,但要避免模糊或开放式表述。
2.
共同评审
:与提出工作需求的人、执行工作的人以及验证工作的人(如测试人员、项目经理、业务分析师等)一起评审该陈述。确保每个人都理解工作的范围和不包含的内容,以及如何验证工作是否完成。
3.
进度审查
:根据需要审查正在进行的工作,确保方向正确。
4.
成果验收
:工作完成后,与利益相关者一起审查,确保他们满意。
对于频繁的常见任务,可以一次性定义并重复使用。例如,创建用户账户无需每次都经历完整的流程,但明确定义任务并设定验收标准是很有用的。这些任务最好实现自动化,以确保步骤一致、可重复且透明,并在之后自动验证标准。
2. 自动化测试的真相与策略
自动化测试往往是许多团队面临的挑战,为现有系统实现一套完整的自动化回归测试极为困难。实际上,编写自动化测试最有价值的成果不是测试本身,而是被测试的代码。编写测试的过程迫使我们思考和质疑所测试系统组件的设计,从而改进该组件的设计。
因此,我们应该在设计和构建系统的过程中编写测试,而不是在完成构建后再进行。因为在没有测试的情况下构建的系统很难添加自动化测试,良好的自动化测试需要能够隔离系统的每个部分并单独进行测试。
成功进行自动化测试的秘诀在于明确目标,应将其视为支持设计、构建和改进基础设施的手段,而非保证基础设施正确性的方法。
3. 测试驱动开发(TDD)
测试驱动开发(TDD)是极限编程(XP)的核心实践。其理念是,由于编写测试可以改进我们构建的系统的设计,所以应该在编写被测试代码之前编写测试。经典的工作节奏如下:
1. 编写测试
2. 运行测试,确保测试失败
3. 编写代码
4. 运行测试,确保测试通过
5. 提交代码和测试
6. 确保更改在持续集成(CI)中通过
在编写代码之前运行测试并确保其失败,是为了证明测试的有效性。如果在进行代码更改之前测试就通过了,那么它实际上并没有测试我们计划进行的更改。
3.1 一次只做一个测试
曾经有一个团队在使用 TDD 时遇到困难,他们在开始构建系统的每个组件之前就编写了所有测试。这种方法类似于过度详细的前期大设计(BDUF)规范的问题,一旦开始构建,测试可能需要大幅更改。因此,应该一次专注于一个操作,编写测试,实现操作,然后再进行下一个。
3.2 测试也是代码
编写和运行测试的工具应与基础设施中的其他部分同等对待,应能够以可重复、可重现、透明的方式设置测试代理、软件等。测试应存储在可提交到版本控制系统(VCS)的外部化格式中,而不是隐藏在专有黑盒工具中。这样,测试的更改可以自动触发 CI 中的测试运行,证明其有效性。
3.3 保持测试代码与被测试代码在一起
测试应与被测试的代码一起管理,将它们放在 VCS 中,并在管道中一起推进,直到运行阶段。这样可以避免测试与代码不匹配的问题。例如,如果为 Chef 食谱编写测试,当食谱发生更改时,测试可能也需要更改。如果测试单独存储,可能会导致混淆和不稳定的构建。
3.4 养成 TDD 习惯
许多人在采用 TDD 时会遇到困难,作者自己也曾如此。开始时,编写测试会减慢开发速度,需要花费大量时间处理测试工具、库、模拟和设置数据等。但当与习惯 TDD 的团队合作时,会发现编写测试变得自然。TDD 有一个学习曲线,需要自律或与有纪律的团队合作,克服困难后,工作效率会提高。养成 TDD 习惯的人可以避免构建混乱、有隐藏问题的代码,并且敢于清理遇到的混乱或损坏的代码,因为测试会捕捉到错误,形成良性循环。
4. 为现有基础设施和系统添加测试
为没有测试的系统或基础设施添加自动化测试具有挑战性。系统的设计可能不适合分层测试,组件可能难以单独设置和测试,也可能难以自动管理测试场景的数据和集成点。
因此,启动一个以构建全面测试套件为目标的项目不太可能成功。实施自动化测试的目标应该是使编写测试成为团队工作习惯的核心部分。团队在进行日常工作时,如添加新服务、修复问题或进行改进,都应考虑并实施自动化测试。
在开始时,这些任务可能会比平时花费更多时间,可能需要设置新的工具,人员需要学习如何使用工具编写测试,并且可能需要重构被测试的系统部分以支持测试。通常,团队在重建或重新平台化基础设施时引入自动化测试是一个好时机,例如从传统的服务器管理模式迁移到基础设施即代码模式,或者迁移到云平台。如果项目是逐步交付的,那么团队也可以逐步添加测试。
5. 测试金字塔
管理测试套件是一项挑战,我们希望测试套件运行快速、易于维护,并能帮助我们快速找出失败的原因。测试金字塔是一种思考如何平衡不同类型测试以实现这些目标的概念。
5.1 测试金字塔结构
| 层级 | 测试范围 | 测试类型 | 特点 |
|---|---|---|---|
| 底层 | 狭窄 | 单元测试,如测试应用代码的一个类或极少数类、Chef 食谱、Puppet 清单等 | 运行速度快,数量多,能提供快速、具体的反馈。如果代码中的错误导致单元测试失败,能迅速知道错误所在位置。 |
| 中层 | 适中 | 验证某些组件、服务和请求的集成 | 例如,通过构建完整的虚拟机并检查其是否符合使用标准来验证服务器配置过程。 |
| 顶层 | 广泛 | 测试系统的大部分,验证各种组件、系统和服务的正确集成。对于应用程序,通常与用户界面交互,模拟用户在应用程序中的操作流程;对于基础设施,可能测试端到端的服务请求。 | 运行时间长,比底层单元测试更复杂。例如,如果服务目录包括为开发团队提供新的 Jira 实例的功能,测试可以执行完整的配置操作并证明 Jira 实例可正常运行。 |
5.2 测试金字塔的 mermaid 流程图
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(顶层测试 - 广泛范围):::process --> B(中层测试 - 适中范围):::process
B --> C(底层测试 - 狭窄范围):::process
6. 平衡测试套件
常见的测试反模式是冰淇淋锥或倒置金字塔,即顶层测试过多。这种测试套件难以维护,运行缓慢,且不如平衡的测试套件能准确找出错误。
用户界面(UI)级别的测试往往比较脆弱,系统的一个更改可能会导致大量 UI 测试失败,修复这些测试的工作量可能比原始更改还要大。这会导致测试套件落后于开发进度,无法持续运行。而且,UI 测试运行速度比底层单元测试慢,难以频繁运行完整套件。当 UI 测试失败时,可能需要花费一些时间来追踪和修复原因。
这种情况通常是因为团队将基于 UI 的测试自动化工具作为测试自动化策略的核心,而测试人员没有参与系统的构建,无法开发底层测试并将其融入构建和变更流程。对于只将系统视为黑盒的人来说,UI 是与系统交互的最简单方式。
在实践中,应尽量减少 UI 和其他高级测试,仅在底层测试运行后再运行。许多软件团队会运行少量端到端的流程测试,确保覆盖系统的主要组件并验证集成是否正确。特定的功能和特性应在组件级别或通过单元测试进行测试。
当高级测试失败或发现错误时,应寻找在尽可能低的测试级别捕获再次出现的错误的方法,以确保快速捕获错误并明确失败位置。如果无法在单元测试级别检测到错误,可以在堆栈的上一层尝试捕获。
例如,如果发现应用程序错误是由于 Chef 编写的配置文件中的错误导致的,不应在运行的应用程序中测试错误是否出现,而应编写测试来验证构建该配置文件的 Chef 食谱。使用像 chefspec 这样的工具可以模拟 Chef 运行食谱的过程,而无需应用到服务器上。以下是一个 chefspec 测试的示例:
require 'chefspec'
describe 'creating the configuration file for our_app' do
let(:chef_run) { ChefSpec::Runner.new.converge('our_app_recipe') }
it 'creates the right file' do
expect(chef_run).to create_template('/etc/our_app.yml')
end
it 'gives the file the right attributes' do
expect(chef_run).to create_template('/etc/our_app.yml').with(
user: 'ourapp',
group: 'ourapp'
)
end
it 'sets the no_crash option'
expect(chef_run).to render_file('/etc/our_app.yml').with_content('no_crash: true')
end
end
7. 管理测试套件
维护自动化测试套件可能是一项负担。随着团队经验的增加,编写和调整测试会变得常规化,但测试套件可能会不断增长。因此,团队需要养成修剪测试的习惯,以保持套件的可管理性,并避免一开始就实施不必要的测试。
测试金字塔建议为每个组件的每个集成级别都编写测试,许多团队最初会选择并实施一系列测试自动化工具来覆盖所有这些级别,但这可能会使测试套件过于复杂。因此,应只实施所需的测试层,避免过度复杂化。
8. 自动化测试的最佳实践总结
为了更清晰地呈现自动化测试中的关键要点和操作步骤,下面将以表格和列表的形式进行总结,方便大家在实际工作中参考。
8.1 自动化测试关键操作步骤总结
| 操作类型 | 具体步骤 |
|---|---|
| 故事流程 |
1. 明确工作内容,用清晰且非模糊开放式的语句表述。
2. 与需求方、执行方和验证方共同评审工作内容,明确工作范围和验证方式。 3. 根据需要审查工作进度。 4. 工作完成后与利益相关者审查成果。 |
| 测试驱动开发(TDD) |
1. 编写测试。
2. 运行测试,确保其失败。 3. 编写代码。 4. 运行测试,确保其通过。 5. 提交代码和测试。 6. 确保更改在持续集成(CI)中通过。 |
| 为现有系统添加测试 |
1. 在日常工作(如添加新服务、修复问题、进行改进)中考虑并实施自动化测试。
2. 若需要,设置新的测试工具。 3. 人员学习使用工具编写测试。 4. 重构被测试的系统部分以支持测试。 |
8.2 自动化测试的其他重要实践要点
-
测试代码管理
- 测试应存储在可提交到版本控制系统(VCS)的外部化格式中,方便管理和触发 CI 测试运行。
- 测试代码应与被测试的代码一起管理,避免版本不匹配问题。
-
测试层级选择
- 遵循测试金字塔原则,优先在底层进行测试,减少 UI 和高级测试的使用。
- 当高级测试失败或发现错误时,尝试在尽可能低的测试级别捕获再次出现的错误。
-
测试套件管理
- 养成修剪测试的习惯,避免测试套件过度增长。
- 只实施所需的测试层,避免过度复杂化。
9. 自动化测试流程 mermaid 流程图
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(明确工作需求):::process --> B(编写测试):::process
B --> C{测试是否失败}:::process
C -->|是| D(编写代码):::process
C -->|否| B(编写测试):::process
D --> E{测试是否通过}:::process
E -->|是| F(提交代码和测试):::process
E -->|否| D(编写代码):::process
F --> G(持续集成验证):::process
G -->|通过| H(日常工作持续改进):::process
G -->|失败| D(编写代码):::process
H --> I(定期审查测试套件):::process
I --> J{是否需要修剪测试}:::process
J -->|是| K(修剪测试):::process
J -->|否| H(日常工作持续改进):::process
K --> H(日常工作持续改进):::process
这个流程图展示了从明确工作需求开始,经过测试驱动开发的循环,到持续集成验证,再到日常工作持续改进和定期审查测试套件的完整自动化测试流程。
10. 总结与展望
自动化测试在现代软件开发和基础设施管理中扮演着至关重要的角色。通过遵循测试驱动开发(TDD)的原则,在设计和构建系统的过程中编写测试,可以提高系统的可维护性和可扩展性。测试金字塔的概念帮助我们平衡不同类型的测试,确保测试套件既高效又能准确找出问题。
在实际应用中,我们需要注意避免一些常见的反模式,如冰淇淋锥式的测试套件和过度依赖 UI 测试。同时,养成良好的测试管理习惯,包括将测试代码与被测试代码一起管理、定期修剪测试套件等,有助于保持测试的有效性和可管理性。
未来,随着技术的不断发展,自动化测试也将不断演进。例如,人工智能和机器学习可能会被应用于测试用例的生成和错误预测,进一步提高测试的效率和准确性。我们应该持续关注这些技术趋势,不断优化我们的自动化测试策略和实践。
总之,掌握自动化测试的核心原则和实践方法,将有助于我们构建更加稳定、可靠的软件系统和基础设施。希望本文所介绍的内容能够为大家在自动化测试的道路上提供有益的参考和指导。
超级会员免费看

被折叠的 条评论
为什么被折叠?



