自动化测试框架的构成
常用测试框架类型及框架设计原则
测试分层
自动化测试框架的构成
一个成熟的测试框架主要由 4 部分组成:基础模块、管理模块、运行模块和统计模块
基础模块
- 底层核心驱动库: 一般指用于操作被测试应用程序的第三方库,例如在 Web 端的 Selenium/WebDriver
- 可重用的组件: 一般用来降低开发成本,常见的有时间处理模块、登录模块等
- 对象库: 存储被测试对象的仓库。在实际应用中,常常将页面进行分组,把一个页面上的所有对象放到一个类里,也就是 Page Object 模式
- 配置文件: 包括测试环境的配置和应用程序的配置。测试环境配置,指的是一个功能从开发代码完成到上线,往往要经过几个测试环境的测试,测试环境的配置能够减少环境切换成本;应用程序配置,主要包括被测试程序的一些配置,利用配置文件,可以做到在不更改代码的情况下覆盖相同程序的不同程序配置
管理模块
- 测试数据管理
测试数据,一般指测试用例用到的各种测试数据,它们是为了验证业务正确性而构造的,每一条测试用例一般对应着一组或多组测试数据,测试数据创建一般分为实时创建和事先创建
- 实时创建:是在测试代码运行时才生成的测试数据。其好处有:测试数据是和测试代码耦合的,测试人员不需要关心其创建过程和业务调用链,通常用在测试的公用功能上。例如,给用户绑定银行卡以方便后续支付等。而坏处则是如果调用链太长,耗时会比较久
- 事先创建:是指测试代码运行前就准备好数据文件。其好处是数据拿来即用,几乎不耗费时间,由于没有业务调用,所以这在一定程度上减少了调用失败的风险;坏处则是数据文件本身需要维护,以保持可用性和正确性
- 测试文件管理
测试框架的文件结构应该清晰有序、一目了然。比如,一个测试用例应该对应建立三个文件,分别是:Page 类文件(xxxPage,根据 PO 模型)、测试类文件(testxxxPage)和对象库文件(xxxPageYml)。这三个文件共同描述了一个完整的测试用例,当你看到一个 Page 类时,就应该做到它还有一个对应的测试类
测试文件的结构清晰有助于他人理解测试框架的设计思想,更有利于测试框架的维护和推广
运行模块
主要用于测试用例的组织和运行,一般包括如下部分:
- 测试用例调度,驱动机制
测试框架应能按需组织,调度测试用例生成、执行。举例来说,测试框架可以在运行时根据使用者给定的 Tag 动态挑选要运行的测试用例,并把它们调度执行(可以顺序执行,也可以并发执行,还可以远程执行) - 错误恢复机制
由于测试环境、测试程序、测试代码存在各种不确定因素,测试框架应该具备一定的错误恢复机制。在测试用例执行中,引起错误的类型一般可分为代码/运行导致的错误和环境/依赖导致的错误,测试框架应该能够识别这两种错误并给予相应的处理 - 持续集成支持
测试框架应该能够和 CI 系统低成本集成,包括通过用户输入参数指定运行环境、测试结束后自动生成测试报告等
统计模块
自动化测试统计模块一般包括如下两部分:
- 测试报告
测试报告应该全面,包括测试用例条数统计、测试用例成功/失败百分比、测试用例总执行时间等总体信息。其中,对于单条测试用例,还应该包括测试用例 ID、测试用例运行结果、测试用例运行时间、测试用例所属模块、测试失败时刻系统截图、测试的日志等信息 - 日志模块
测试框架应该包括完善的日志文件,方便出错时进行排查和定位
常用测试框架类型
测试框架有很多类型,比较常见的有以下四类。
- 模块化测试框架
模块化测试框架是利用 OOP 思想和 PO 模式改造而来的框架
模块化测试框架把整个测试分为多个模块,模块化有以下几个特征:
- 将一个业务或者一个页面成为一个 Page 对象
- 这个 Page 对象,以一个 Page 类来表示它
- Page 类里存放有所有这个 Page所属的页面对象、元素操作;
- 页面对象和元素操作组成一个个的测试类方法,供测试用例层调用
模块化测试框架的好处在于方便维护,坏处在于你需要非常了解你的系统及这些模块是如何划分的,才能在测试脚本里自如地使用,否则你就会陷入重复定义模块对象的循环里
-
数据驱动框架
数据驱动框架主要解决了测试数据的问题
在测试中,我们常常需要为同一个测试逻辑,构造不同的测试数据以满足业务需求,这些测试数据可以保存在测试代码里,也可以保存在外部文件里(包括 Excel、File、DB)
数据驱动框架的精髓在于,输入 M 组数据,框架会自动构造出 M 个测试用例,并在测试结果中把每一个测试用例的运行结果独立展示出来。
在 Python 架构里,最出名的数据驱动框架就是 DDT -
关键字驱动框架
关键字驱动其实就是把一系列代码操作封装成一个关键字(这个关键字其实是函数名),在测试里,可以通过使用组合关键字的方式来生成测试用例,而不去关心这个关键字是如何运作的
关键字的一个典型应用是将登录操作封装为关键字 Login,之后在后续代码里,有关 Login的操作,就仅需调用这个关键字 Login,而不必又重新进行一次登录操作
关键字在领域里的最佳应用典范我认为是BDD(行为驱动开发),它甚至被当成一种独立的敏捷软件开发技术来使用 -
混合模型
没有任何规定要求你的测试框架要属于以上某种类型,因为测试框架的存在不是为了分类型,而是为了更好地测试,所以在工作中,常常需要糅合不同框架模型,可以将这种模式的测试框架称为混合模型。混合模型可以包含模块化框架,也可以使用数据驱动,或者使用 BDD 模式
自动化测试框架设计原则
- 清晰明了,学习成本低
- 通用性强,可维护可扩展
- 通用性强:通用于不同的操作系统,可解决同一类通用问题
- 可维护:可维护性无法用具体的指标衡量,但可以感知的,因为不可维护性常常以代码逻辑混乱,不遵循编码规则等特征出现
- 可扩展:可扩展性指当需求变化时框架容易扩展
- 对错误处理能力强
- 错误处理机制,高效解决:在测试运行中,难免由于种种原因运行错误,这时测试框架就必须具备处理错误的能力。错误处理机制一般分为停止运行和错误恢复两种
- 停止运行是指发现错误后直接停止本次测试,在实践中一般在测试框架本身出现错误的时候才会使用
- 针对具体的测试用例执行,错误恢复这种方式比较常见。其步骤通常是标记当前用例为“失败”,清理失败数据,恢复测试环境,然后再运行下一条测试。根据错误恢复的时机又可以分为事先恢复(当前用例运行前,将环境和数据恢复为初始状态)和事后恢复(当前用例执行完成后,将环境和数据恢复为初始状态)两种,其中事先恢复现在是比较常用的
- 运行效率高且功能强大
- 支持测试环境切换
- 支持外部数据驱动
- 支顺序、并发、远程运行
- 报告完备详尽
- 支持版本控制和持续集成
测试分层
来自专业名称“Test Pyramid”,也就是我们常说的“测试金字塔”,是 Martin Fowler 在 2012 年提出的一个概念
“测试金字塔”将软件测试分为不同的粒度,强调了不同粒度的自动化测试在整个自动化测试中的占比应该不同,旨在指导我们如何使用不同类型的自动化测试来实现软件测试价值的最大化
- Unit 层(单元测试层)
主要关注函数,类级别的测试;单元测试之间相互没有依赖,是独立的,可重复执行的;单元测试的执行时间最短,成本最低 - Service 层(服务层)
主要关注模块本身,模块与模块集成的接口, 子系统本身, 各个子系统之间的测试;Server 层的测试可涉及框架、数据库、第三方服务等 - UI层
关注从用户角度看, 整个系统的表现和交互;UI 层的测试通常通过操作页面对象来执行;耗时最长,成本最高
分层测试的发展
- 原本的 Service 层进一步细分为组件测试(Component Test)、集成测试(Integration Test)和 API 接口测试(API Testing)
- UI 层,则被 E2E(End To End)测试取代:UI 测试的重点在于产品或系统的 UI 部分;E2E 则更关注整个产品或者系统的行为是否正确
- Unit(单元测试)层
- Component Test(组件测试)
这部分是 Unit 层的组装,多个 unit 组成一个 Component - Integration Test(集成测试)
把多个 Component(组件)形成一个子系统或者系统,集成测试分自顶向下集成和自底向上集成 - API Test(接口测试)
- E2E 测试
- 探索性测试
分层测试误区及最佳实践
- 分层测试并没有规定每一层测试的先后顺序,在实践中,每一层的测试是没有执行先后顺序的,是可以同时运行的
- 可跨层执行测试,如在 E2E 测试时调用接口来迅速构造数据(即在E2E 层调用 API),或者使用 Mock 绕过某些非目标测试场景
- 不要重复测试
同样一个检查点,在 Unit 层有测试用例,在 Service 层也有测试用例,在 E2E 测试里也有覆盖。理想的情况是,每一个层次的测试用例集合起来,正好是最小的,能覆盖所有需求的测试集。重复测试坏处在于,如果有改动,那么就要改动 3 次,并且还增加了脚本维护时间,测试成本非常高 - 测试尽量下沉
测试下沉是指能在单元测试层覆盖的,尽量在单元测试层覆盖。它是一个动态的过程。举例来说,假设你发现某一条 E2E 测试发现了一个功能性 Bug,这意味着你的单元测试某处缺失。这时,你需要把针对这个 Bug 的检查下沉到单元测试层,并且删除掉 E2E 层的测试 - 根据业务特性,合理分层
一切测试都是基于风险的,生搬硬套“分层测试”并不能保证你的项目一定会成功。如微服务项目的就适用于纺锤模型,在测试时就需要多关注集成测试,而不必要把单元测试的比重放很高