软件测试52讲 - 笔记(二)- GUI自动化测试篇
12|从0到1:你的第一个GUI自动化测试
Selenium 的实现原理
总结
Selenium 1.0 的核心是,基于 JavaScript 代码注入;而 Selenium 2.0 的核心是,运用了浏览器原生支持的 WebDriver。
Selenium 1.0 的工作原理
Selenium 1.0,又称 Selenium RC,其中 RC 是 Remote Control 的缩写。Selenium RC 利用的原理是:JavaScript 代码可以很方便地获取页面上的任何元素并执行各种操作。
Http Proxy 模块就是用来“欺骗”浏览器,要想在测试用例运行中的浏览器中,注入 JavaScript 代码从而实现自动化的 Web 操作,Selenium RC 就必须“欺骗”被测站点,让它误以为被注入的代码是同源的。
Selenium RC Server,主要包括 Selenium Core,Http Proxy 和 Launcher 三部分:
- Selenium Core,是被注入到浏览器页面中的 JavaScript 函数集合,用来实现界面元素的识别和操作;
- Http Proxy,作为代理服务器修改 JavaScript 的源,以达到“欺骗”被测站点的目的;
- Launcher,用来在启动测试浏览器时完成 Selenium Core 的注入和浏览器代理的设置。Client Libraries,是测试用例代码向 Selenium RC Server 发送 Http 请求的接口,支持多种语言,包括 Java、C# 和 Ruby 等。
Selenium RC 的执行流程
- 测试用例通过基于不同语言的 Client Libraries 向 Selenium RC Server 发送 Http 请求,要求与其建立连接。
- 连接建立后,Selenium RC Server 的 Launcher 就会启动浏览器或者重用之前已经打开的浏览器,把 Selenium Core(JavaScript 函数的集合)加载到浏览器页面当中,并同时把浏览器的代理设置为 Http Proxy。
- 测试用例通过 Client Libraries 向 Selenium RC Server 发送 Http 请求,Selenium RC Server 解析请求,然后通过 Http Proxy 发送 JavaScript 命令通知 Selenium Core 执行浏览器上控件的具体操作。
- Selenium Core 接收到指令后,执行操作。
- 如果浏览器收到新的页面请求信息,则会发送 Http 请求来请求新的 Web 页面。由于 Launcher 在启动浏览器时把 Http Proxy 设置成为了浏览器的代理,所以 Selenium RC Server 会接收到所有由它启动的浏览器发送的请求。
- Selenium RC Server 接收到浏览器发送的 Http 请求后,重组 Http 请求以规避“同源策略”,然后获取对应的 Web 页面。
- Http Proxy 把接收的 Web 页面返回给浏览器,浏览器对接收的页面进行渲染。
Selenium 2.0 的工作原理
Selenium 2.0,又称 Selenium WebDriver,它利用的原理是:使用浏览器原生的 WebDriver 实现页面操作。
Selenium WebDriver 是典型的 Server-Client 模式,Server 端就是 Remote Server。以下是 Selenium 2.0 工作原理的解析。
- 当使用 Selenium2.0 启动浏览器 Web Browser 时,后台会同时启动基于 WebDriver Wire 协议的 Web Service 作为 Selenium 的 Remote Server,并将其与浏览器绑定。
- 绑定完成后,Remote Server 就开始监听 Client 端的操作请求。执行测试时,测试用例会作为 Client 端,将需要执行的页面操作请求以 Http Request 的方式发送给 Remote Server。该 HTTP Request 的 body,是以 WebDriver Wire 协议规定的 JSON 格式来描述需要浏览器执行的具体操作。
- Remote Server 接收到请求后,会对请求进行解析,并将解析结果发给 WebDriver,由 WebDriver 实际执行浏览器的操作。
- WebDriver 可以看做是直接操作浏览器的原生组件(Native Component),所以搭建测试环境时,通常都需要先下载浏览器对应的 WebDriver。
13|效率为王:脚本与数据的解耦 + Page Object模型
总结
“测试脚本和数据解耦”的本质是实现了数据驱动的测试,让操作相同但是数据不同的测试可以通过同一套自动化测试脚本来实现,只是在每次测试执行时提供不同的测试输入数据。
“页面对象模型”的核心理念是,以页面为单位来封装页面上的控件以及控件的部分操作。而测试用例使用页面对象来完成具体的界面操作。
测试脚本和数据的解耦
测试数据和测试脚本分离
测试脚本只有一份,其中需要输入数据的地方会用变量来代替,然后把测试输入数据单独放在一个文件中。这个存放测试输入数据的文件,通常是表格的形式,也就是最常见的 CSV 文件。
测试脚本中通过 data provider 去 CSV 文件中读取一行数据,赋值给相应的变量,执行测试用例。接着再去 CSV 文件中读取下一行数据,读取完所有的数据后,测试结束。CSV 文件中有几行数据,测试用例就会被执行几次。
数据驱动测试
以下图片也就是数据驱动(Data-driven)测试。
优势
- 数据驱动很好地解决了大量重复脚本的问题,实现了“测试脚本和数据的解耦”。 目前几乎所有成熟的自动化测试工具和框架,都支持数据驱动的测试,而且除了支持 CSV 这种最常见的数据源外,还支持 xls 文件、JSON 文件,YAML 文件,甚至还有直接以数据库中的表作为数据源的,比如 QTP 就支持以数据库中的表作为数据驱动的数据源。
- 数据驱动测试的数据文件中不仅可以包含测试输入数据,还可以包含测试验证结果数据,甚至可以包含测试逻辑分支的控制变量。 上图中的“Result_LoginSuccess_Flag”变量其实就是用户分支控制变量。
- 数据驱动测试的思想不仅适用于 GUI 测试,还可以用于 API 测试、接口测试、单元测试等。 所以,很多 API 测试工具(比如 SoapUI),以及单元测试框架都支持数据驱动测试,它们往往都是通过 Test Data Provider 模块将外部测试数据源逐条“喂”给测试脚本。
页面对象(Page Object)模型
脚本通常是由一系列的页面控件的顺序操作组成的。
GUI测试常见问题
- 脚本逻辑层次不够清晰,属于 All-in-one 的风格,既有页面元素的定位查找,又有对元素的操作。
- 脚本的可读性差。
- 由于脚本的每一行都直接描述各个页面上的元素操作,你很难一眼看出脚本更高层的业务测试流程。
- 通用步骤会在大量测试脚本中重复出现。
如何解决
利用模块化思想,把一些通用的操作集合打包成一个个名字有意义的函数,然后 GUI 自动化脚本直接去调用这些操作函数来构成整个测试用例,这样 GUI 自动化测试脚本就从原本的“流水账”过渡到了“可重用脚本片段”。
模块化设计思想带来的好处
- 解决了脚本可读性差的问题,脚本的逻辑层次也更清晰了;
- 解决了通用步骤会在大量测试脚本中重复出现的问题
但是,还存在问题:每个操作函数内部的脚本可读性问题依然存在,而且还引入了新的问题,即如何把控操作函数的粒度,以及如何衔接两个操作函数之间的页面。
因此,需要未解决这个问题,那就需要了解“页面对象模型”
页面对象模型的核心理念是,以页面(Web Page 或者 Native App Page)为单位来封装页面上的控件以及控件的部分操作。而测试用例,更确切地说是操作函数,基于页面封装对象来完成具体的界面操作,最典型的模式是“XXXPage.YYYComponent.ZZZOperation”。
14|更接近业务的抽象:让自动化测试脚本更好地描述业务
总结
业务流程抽象是,基于操作函数的更接近于实际业务的更高层次的抽象方式。基于业务流程抽象实现的测试用例往往具有较好的灵活性,可以根据实际测试需求方便地组装出各种测试用例。
业务流程的核心思想是,从业务的维度来指导测试业务流程的封装。由于业务流程封装通常很贴近实际业务,所以特别适用于组装面向终端用户的端到端(E2E)的系统功能测试用例,尤其适用于业务功能非常多,并且存在各种组合的 E2E 测试场景。
操作函数的粒度
操作函数的粒度是指,一个操作函数到底应该包含多少操作步骤才是最合适的。
不好
- 粒度太大,就会降低操作函数的可重用性。
- 粒度太小,也就失去了操作函数封装的意义。
- 测试工程师对操作函数的粒度理解也不完全相同,会导致同一个项目中粒度不一致。
实际
脚本粒度的控制往往以完成一个业务流程(business flow)为主线,抽象出其中的“高内聚低耦合”的操作步骤集合,操作函数就由这些操作步骤集合构成。
衔接两个操作函数之间的页面
前序操作函数完成后的最后一个页面,必须是后续操作函数的第一个页面。
如果连续的两个操作函数之间无法用页面衔接,那就需要在两个操作函数之间加入额外的页面跳转代码。
业务流程抽象
业务流程抽象是,基于操作函数的更接近于实际业务的更高层次的抽象方式。基于业务流程抽象实现的测试用例往往灵活性会非常好,你可以很方便地组装出各种测试用例。
小结
对于每一个业务流程类,都会有相应的业务流程输入参数类与之一一对应。具体的步骤通常有这么几步:
- 初始化一个业务流程输入参数类的实例;
- 给这个实例赋值;
- 用这个输入参数实例来初始化业务流程类的实例;
- 执行这个业务流程实例。
(执行业务流程实例的过程,其实就是调用操作函数来完成具体的页面对象操作的过程)
业务流程的优点
- 业务流程(Business Flow)的封装更接近实际业务;
- 基于业务流程的测试用例非常标准化,遵循“参数准备”、“实例化 Flow”和“执行 Flow”这三个大步骤,非常适用于测试代码的自动生成;
- 由于更接近实际业务,所以可以很方便地和 BDD 结合(BDD,Behavior Driven Development,行为驱动开发)
15 |过不了的坎:聊聊GUI自动化过程中的测试数据
总结
实际测试项目中,往往需要综合运用 API 调用和数据库操作来创建测试数据,并且会根据测试数据自身的特点,分而治之地采用 On-the-fly 和 Out-of-box 的方式,以寻求数据稳定性和数据准备效率之间的最佳平衡。
在实际项目中,对于创建数据的技术手段而言,最佳的选择是利用 API 来创建数据,只有当 API 不能满足数据创建的需求时,才会使用数据库操作的手段。
基于 API 调用创建测试数据
不好
- 不是所有的测试数据都有相关的 API 来支持。
- 对需要大量创建数据的测试来说,基于 API 调用方式的执行效率,即使采用了并发机制也不会十分理想。
基于数据库操作创建测试数据
数据的创建和修改直接在产品代码内完成,而且并没有对外暴露供测试使用的接口,就需要通过直接操作数据库的方式来产生测试数据。
好处
可以创建和修改 API 不支持的测试数据,并且由于是直接数据库操作,执行效率会远远高于 API 调用方法。
缺点
数据库操作这种方式的缺点也显而易见,数据库表操作的任何变更,都必须同步更新测试数据工具中的 SQL 语句。
但是,经常出现因为 SQL 语句更新不及时而导致测试数据错误的问题,而且这里的数据不准确往往只是局部错误,因此这类问题往往比较隐蔽。
因此,需要版本管理。
综合运用 API 调用和数据库操作创建测试数据
当你要创建一种特定的测试数据时,你发现没有直接 API 支持,但是可以通过 API 先创建一个基本的数据,然后再通过修改数据库的方式来更新这个数据,以此来达到创建特定测试数据的要求。
实时创建数据:On-the-fly
GUI 测试脚本中,在开始执行界面操作前,我们往往会通过调用测试数据工具实时创建测试数据,也就是 On-the-fly 方式。
存在三个问题
- 在用例执行过程中实时创建数据,导致测试的执行时间比较长。
- 业务数据的连带关系,导致测试数据的创建效率非常低。
- 更糟糕的情况是,实时创建测试数据的方式对测试环境的依赖性很强。
事先创建测试数据:Out-of-box
Out-of-box 的含义是开箱即用,也就是说,已经在被测系统中预先创建好了充足的、典型的测试数据。这些数据通常是在搭建测试环境时通过数据库脚本“预埋”在系统中的,后续的测试用例可以直接使用。
存在三个问题
- 测试用例中需要硬编码(hardcode)测试数据,额外引入了测试数据和用例之间的依赖。
- 只能被一次性使用的测试数据不适合 Out-of-box 的方式。
- “预埋”的测试数据的可靠性远不如实时创建的数据。
On-the-fly 和 Out-of-box 的互补
选择什么时机创建测试数据?
- 对于相对稳定、很少有修改的数据,建议采用 Out-of-box 的方式,比如商品类目、厂商品牌、部分标准的卖家和买家账号等。
- 对于一次性使用、经常需要修改、状态经常变化的数据,建议使用 On-the-fly 的方式。
- 用 On-the-fly 方式创建测试数据时,上游数据的创建可以采用 Out-of-box 方式,以提高测试数据创建的效率。以订单数据为例,订单的创建可以采用 On-the-fly 方式,而与订单相关联的卖家、买家和商品信息可以使用 Out-of-box 方式创建。
16|脑洞大开:GUI测试还能这么玩(Page Code Gen + Data Gen + Headless)?
总结
- 对于页面对象自动生成,商用测试软件已经实现了这个功能。但是,如果你选择开源测试框架,就需要自己实现这个功能了。
- GUI 测试数据自动生成,主要是基于测试输入数据的类型以及对应的自定义规则库实现的,并且对于多个测试输入数据,可以基于笛卡尔积来自动组合出完整的测试用例集合。
- 对于无头浏览器,你可以把它简单地想象成运行在内存中的浏览器,它拥有完整的浏览器内核。与普通浏览器最大的不同是,它在执行过程中看不到运行的界面。目前,Headless Chrome 结合 Puppeteer 是最先进的无头浏览器方案,如果感兴趣,你可以下载试用。
「Headless Chrome使用方式,在根目录输入 /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --headless --remote-debugging-port=9222 --disable-gpu http://www.baidu.com」
页面对象自动生成
页面对象自动生成技术,属于典型的“自动化你的自动化”的应用场景。它的基本思路是,你不用再手工维护 Page Class 了,只需要提供 Web 的 URL,它就会自动帮你生成这个页面上所有控件的定位信息,并自动生成 Page Class
注意点
那些依赖于数据的动态页面对象也会被包含在自动生成的 Page Class 里,而这种动态页面对象通常不应该包含在 Page Class 里,所以,往往需要以手工的方式删除。
GUI 测试数据自动生成
GUI 测试数据自动生成,指的由机器自动生成测试用例的输入数据。
仅限于以下2种情况
- 根据 GUI 输入数据类型,以及对应的自定义规则库自动生成测试输入数据
- 对于需要组合多个测试输入数据的场景,测试数据自动生成可以自动完成多个测试数据的笛卡尔积组合,然后再以人工的方式剔除掉非法的数据组合。
无头浏览器
无头浏览器,其实是一个特殊的浏览器,你可以把它简单地想象成是运行在内存中的浏览器。它拥有完整的浏览器内核,包括 JavaScript 解析引擎、渲染引擎等。与普通浏览器最大的不同是,无头浏览器执行过程中看不到运行的界面,但是你依然可以用 GUI 测试框架的截图功能截取它执行中的页面。
主要应用场景
- GUI 自动化测试
- 页面监控
- 网络爬虫
好处
- 测试执行速度更快。 无需加载 CSS 以及渲染页面,用例执行速度快。
- 减少对测试执行的干扰。
- 简化测试执行环境的搭建。
- 在单机环境实现测试的并发执行。
缺点
不能完全模拟真实的用户行为,而且由于没有实际完成页面的渲染,所以不太适用于需要对于页面布局进行验证的场景。
同时,业界也一直缺乏理想的无头浏览器方案。