This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
<audio id="audio" title="12 | 从0到1你的第一个GUI自动化测试" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8c/10/8cf850346c6d5641c5c02c24f84cc110.mp3"></audio>
在前面的测试基础知识系列文章中,我分享了测试相关的基础知识,从测试用例的设计,到测试覆盖率,再到测试计划的制定,这些都是我认为测试人要掌握的一些基本知识。
那么接下来我将要带你走入GUI自动化测试的世界和你一起聊聊GUI自动化测试的技术、原理和行业最佳实践。
作为该系列的第一篇文章我直接以一个最简单的GUI自动化用例的开发为例带你从0开始构建一个Selenium的GUI自动化测试用例。
先让你对GUI自动化测试有一个感性认识然后以此为基础我再来解释Selenium自动化测试实现的核心原理与机制希望可以帮你由点到面建立起GUI测试的基础知识体系。
## 构建一个Selenium自动化测试用例示例
测试需求非常简单:访问百度主页,搜索某个关键词,并验证搜索结果页面的标题是“被搜索的关键词”+“_百度搜索”。
如果搜索的关键词是“极客时间”那么搜索结果页面的标题就应该是“极客时间_百度搜索”。
明白了测试需求后,我强烈建议你先用手工方式执行一遍测试,具体步骤是:
<li>
打开Chrome浏览器输入百度的网址“[www.baidu.com](http://www.baidu.com)”;
</li>
<li>
在搜索输入框中输入关键词“极客时间”并按下回车键;
</li>
<li>
验证搜索结果页面的标题是否是“极客时间_百度搜索”。
</li>
明确了GUI测试的具体步骤后我们就可以用Java代码基于Selenium实现这个测试用例了。
这里我要用到Chrome浏览器所以需要先下载Chrome Driver并将其放入环境变量。接下来你可以用自己熟悉的方式建立一个空的Maven项目然后在POM文件中加入Selenium 2.0的依赖如图1所示。
<img src="https://static001.geekbang.org/resource/image/83/a3/838b15ae28b8e318577705f61d619aa3.png" alt="">
接着用Java创建一个main方法并把如图2所示的代码复制到你的main方法中。
<img src="https://static001.geekbang.org/resource/image/d4/fb/d4c851bf536ce1fa6167afa169ab8bfb.png" alt="">
现在你可以尝试运行这个main方法看看会执行哪些操作。
<li>
这段代码会自动在你的电脑上打开Chrome浏览器
</li>
<li>
在URL栏自动输入“[www.baidu.com](http://www.baidu.com)”;
</li>
<li>
百度主页打开后,在输入框自动输入“极客时间”并执行搜索;
</li>
<li>
返回搜索结果页面;
</li>
<li>
Chrome浏览器自动退出。
</li>
以上这些步骤都是由自动化测试代码自动完成的。
如果你已经接触过GUI自动化测试你可能习以为常了感觉没什么神奇的。但如果你是第一次接触GUI自动化测试是不是觉得还蛮有意思的。
现在,我来快速解读一下这些代码,你可以看看这些自动化步骤是怎么实现的,更具体的原理和内部机制我会在后面文章中详细展开。
- 第11行WebDriver driver = new ChromeDriver()先创建一个Chrome Driver的实例也就是打开了Chrome浏览器但实际上没这么简单后台还做了些额外的Web Service绑定工作具体后面会解释
- 第14行driver.navigate().to(s: “[http://www.baidu.com](http://www.baidu.com)”)用刚才已经打开的Chrome浏览器访问百度主页
- 第18行WebElement search_input = driver.findElement([By.name](http://By.name)(“wd”))使用driver的findElement方法并通过name属性定位到了搜索输入框并将该搜索输入框命名为search_input
- 第21行search_input.sendKeys(…charSequences:“极客时间”)通过WebElement的sendKeys方法向搜索输入框search_input输入了字符串“极客时间”
- 第24行search_input.submit(),递交了搜索请求;
- 第27行Thread.sleep(millis:3000)强行等待了固定的3秒时间
- 第30行Assert.assertEquals(expected:“极客时间_百度搜索”,driver.getTitle())通过junit的assertEquals比较了浏览器的标题与预计结果其中页面标题通过driver的getTitle方法得到如果标题与预计结果一致测试通过否则测试失败
- 第33行driver.quit()显式关闭了Chrome浏览器。
现在你对main方法中的代码已经比较清楚了。但是你知道Selenium内部是如何实现Web自动化操作的吗这就要从Selenium的历史版本和基本原理开始讲起了。
## Selenium的实现原理
首先你要明确刚才建立的测试用例是基于Selenium 2.0也就是Selenium + WebDriver的方案。
其次你需要知道对Selenium而言V1.0和V2.0版本的技术方案是截然不同的V1.0的核心是Selenium RC而V2.0的核心是WebDriver可以说这完全是两个东西。
最后Selenium 3.0也已经发布一段时间了V3.0相比V2.0并没有本质上的变化主要是增加了对MacOS的Safari和Windows的Edge的支持并彻底删除了对Selenium RC的支持。
所以接下来我会针对V1.0和V2.0来解释Selenium实现Web自动化的原理。
**第一Selenium 1.0的工作原理**
Selenium 1.0又称Selenium RC其中RC是Remote Control的缩写。Selenium RC利用的原理是JavaScript代码可以很方便地获取页面上的任何元素并执行各种操作。
但是因为"同源政策Same-origin policy"只有来自相同域名、端口和协议的JavaScript代码才能被浏览器执行所以要想在测试用例运行中的浏览器中注入JavaScript代码从而实现自动化的Web操作Selenium RC就必须“欺骗”被测站点让它误以为被注入的代码是同源的。
那如何实现“欺骗”呢这其实就是引入Selenium RC Server的根本原因其中的Http Proxy模块就是用来“欺骗”浏览器的。
除了Selenium RC ServerSelenium RC方案的另一大部分就是Client Libraries。它们的具体关系如图3所示。
<img src="https://static001.geekbang.org/resource/image/30/b6/30bb6d776cd499b727d83fa4499ca9b6.png" alt="">
**Selenium RC Server主要包括Selenium CoreHttp Proxy和Launcher三部分**
- Selenium Core是被注入到浏览器页面中的JavaScript函数集合用来实现界面元素的识别和操作
- Http Proxy作为代理服务器修改JavaScript的源以达到“欺骗”被测站点的目的
- Launcher用来在启动测试浏览器时完成Selenium Core的注入和浏览器代理的设置。
**Client Libraries是测试用例代码向Selenium RC Server发送Http请求的接口支持多种语言**包括Java、C#和Ruby等
为了帮你更好地理解Selenium RC的基本原理我从Selenium的官方网站截取了以下执行流程图并把具体的7个步骤做了如下翻译。
<img src="https://static001.geekbang.org/resource/image/9c/69/9c8317d792a8798b3f2cdedf80ff2e69.png" alt="">
<li>
测试用例通过基于不同语言的Client Libraries向Selenium RC Server发送Http请求要求与其建立连接。
</li>
<li>
连接建立后Selenium RC Server的Launcher就会启动浏览器或者重用之前已经打开的浏览器把Selenium CoreJavaScript函数的集合加载到浏览器页面当中并同时把浏览器的代理设置为Http Proxy。
</li>
<li>
测试用例通过Client Libraries向Selenium RC Server发送Http请求Selenium RC Server解析请求然后通过Http Proxy发送JavaScript命令通知Selenium Core执行浏览器上控件的具体操作。
</li>
<li>
Selenium Core接收到指令后执行操作。
</li>
<li>
如果浏览器收到新的页面请求信息则会发送Http请求来请求新的Web页面。由于Launcher在启动浏览器时把Http Proxy设置成为了浏览器的代理所以Selenium RC Server会接收到所有由它启动的浏览器发送的请求。
</li>
<li>
Selenium RC Server接收到浏览器发送的Http请求后重组Http请求以规避“同源策略”然后获取对应的Web页面。
</li>
<li>
Http Proxy把接收的Web页面返回给浏览器浏览器对接收的页面进行渲染。
</li>
**第二Selenium 2.0的工作原理**
接下来我们回到上面那个百度搜索的测试用例这个测试用例用的就是Selenium 2.0。Selenium 2.0又称Selenium WebDriver它利用的原理是使用浏览器原生的WebDriver实现页面操作。它的实现方式完全不同于Selenium 1.0。
Selenium WebDriver是典型的Server-Client模式Server端就是Remote Server。以下是Selenium 2.0工作原理的解析。
<img src="https://static001.geekbang.org/resource/image/55/26/5536bccaa266329c324fa9033ee16826.png" alt="">
<li>
当使用Selenium2.0启动浏览器Web Browser时后台会同时启动基于WebDriver Wire协议的Web Service作为Selenium的Remote Server并将其与浏览器绑定。绑定完成后Remote Server就开始监听Client端的操作请求。
</li>
<li>
执行测试时测试用例会作为Client端将需要执行的页面操作请求以Http Request的方式发送给Remote Server。该HTTP Request的body是以WebDriver Wire协议规定的JSON格式来描述需要浏览器执行的具体操作。
</li>
<li>
Remote Server接收到请求后会对请求进行解析并将解析结果发给WebDriver由WebDriver实际执行浏览器的操作。
</li>
<li>
WebDriver可以看做是直接操作浏览器的原生组件Native Component所以搭建测试环境时通常都需要先下载浏览器对应的WebDriver。
</li>
## 总结
首先我基于Selenium 2.0带你从0到1建立了一个最简单直接的GUI自动化测试用例。这个用例的实现很简单但是只有真正理解了Selenium工具的原理你才能真正用好它。
所以我又分享了Selenium 1.0和Selenium 2.0的内部实现机制和原理Selenium 1.0的核心是基于JavaScript代码注入而Selenium 2.0的核心是运用了浏览器原生支持的WebDriver。
## 思考题
除了Selenium业内还有很多常用的GUI自动化测试框架比如UFT以前的QTP、RFT、Nightwatch等你在平时的工作中接触过哪些GUI自动化测试框架你知道它们的内部实现原理吗
欢迎你给我留言。

View File

@@ -0,0 +1,122 @@
<audio id="audio" title="13 | 效率为王:脚本与数据的解耦 + Page Object模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c7/d4/c799ac7f088e842cf54845b9d0e333d4.mp3"></audio>
在上一篇文章中我用Selenium 2.0实现了我们的第一个GUI自动化测试用例在你感觉神奇的同时是否也隐隐感到一丝丝的担忧呢比如测试脚本中既有测试数据又有测试操作所有操作都集中在一个脚本中等等。
那么今天我就通过介绍GUI测试中两个非常重要的概念测试脚本和数据的解耦以及页面对象Page Object模型带你看看如何优化这个测试用例。
## 测试脚本和数据的解耦
我在前面的文章中和你分享过GUI自动化测试适用的场景它尤其适用于需要回归测试页面功能的场景。那么你现在已经掌握了一些基本的GUI自动化测试用例的实现方法是不是正摩拳擦掌准备批量开发GUI自动化脚本把自己从简单、重复的GUI界面操作中解放出来呢
但是你很快就会发现如果在测试脚本中硬编码hardcode测试数据的话测试脚本灵活性会非常低。而且对于那些具有相同页面操作而只是测试输入数据不同的用例来说就会存在大量重复的代码。
举个最简单的例子上一篇文章中实现的百度搜索的测试用例当时用例中搜索的关键词是“极客时间”假设我们还需要测试搜索关键词是“极客邦”和“InfoQ”的场景如果不做任何处理那我们就可能需要将之前的代码复制3份每份代码的主体完全一致只是其中的搜索关键词和断言Assert的预期结果不同。
显然,这样的做法是低效的。
更糟糕的是界面有任何的变更需要修改自动化脚本时你之前复制出来的三个脚本都需要做相应的修改。比如搜索输入框的名字发生了变化你就需要修改所有脚本中findElement方法的by.name属性。
而这里只有三个脚本还好如果有30个或者更多的脚本呢你会发现脚本的维护成本实在是太高了。那么这种情况应该怎么处理呢
相信你现在已经想到了把测试数据和测试脚本分离。也就是说测试脚本只有一份其中需要输入数据的地方会用变量来代替然后把测试输入数据单独放在一个文件中。这个存放测试输入数据的文件通常是表格的形式也就是最常见的CSV文件。
然后在测试脚本中通过data provider去CSV文件中读取一行数据赋值给相应的变量执行测试用例。接着再去CSV文件中读取下一行数据读取完所有的数据后测试结束。CSV文件中有几行数据测试用例就会被执行几次。具体流程如图1所示。
<img src="https://static001.geekbang.org/resource/image/66/bb/664dfda4bee44e79b2437f68b2a06bbb.png" alt="" />
这也就是典型的数据驱动Data-driven测试了。
<li>
**数据驱动很好地解决了大量重复脚本的问题,实现了“测试脚本和数据的解耦”。** 目前几乎所有成熟的自动化测试工具和框架都支持数据驱动的测试而且除了支持CSV这种最常见的数据源外还支持xls文件、JSON文件YAML文件甚至还有直接以数据库中的表作为数据源的比如QTP就支持以数据库中的表作为数据驱动的数据源。
</li>
<li>
**数据驱动测试的数据文件中不仅可以包含测试输入数据,还可以包含测试验证结果数据,甚至可以包含测试逻辑分支的控制变量。** 图1中的“Result_LoginSuccess_Flag”变量其实就是用户分支控制变量。
</li>
<li>
**数据驱动测试的思想不仅适用于GUI测试还可以用于API测试、接口测试、单元测试等。** 所以很多API测试工具比如SoapUI以及单元测试框架都支持数据驱动测试它们往往都是通过Test Data Provider模块将外部测试数据源逐条“喂”给测试脚本。
</li>
## 页面对象Page Object模型
为了让你了解“页面对象Page Object模型”这个概念的来龙去脉并能够深入理解这个概念的核心思想我会先从早期的GUI自动化测试开始讲起。
早期的GUI自动化测试脚本无论是用开源的Selenium开发还是用商用的QTPQuick Test Professional现在已经改名为Unified Functional Testing开发脚本通常是由一系列的页面控件的顺序操作组成的如图2所示的伪代码展示了一个典型的早期GUI测试脚本的结构。
<img src="https://static001.geekbang.org/resource/image/8a/a3/8a7a697eac4f0573eea52dcfe05768a3.png" alt="" />
我先来简单介绍一下这个脚本实现的功能。
- 第1-4行输入用户名和密码并点击“登录”按钮登录完成后页面将跳转至新页面
- 第5行在新页面找到“图书”链接然后点击链接跳转至图书的页面
- 第7-10行在图书搜索框输入需要查找的书名点击“搜索”按钮然后通过assert验证搜索结果
- 第11-12行用户登出。
看完这段伪代码,你是不是觉得脚本有点像操作级别的“流水账”,而且可读性也比较差,这主要体现在以下几个方面:
- **脚本逻辑层次不够清晰属于All-in-one的风格既有页面元素的定位查找又有对元素的操作。**
<li>**脚本的可读性差。** 为了方便你理解示例中的代码用了比较直观的findElementByName你可以很方便地从name的取值比如“username”和“password”猜出脚本所执行的操作。<br />
但在实际代码中很多元素的定位都会采用Xpath、ID等方法此时你就很难从代码中直观看出到底脚本在操作哪个控件了。也就是说代码的可读性会更差带来的直接后果就是后期脚本的维护难度增大。<br />
有些公司自动化测试脚本的开发和维护是两拨人,脚本开发并调试完以后,开发人员就会把脚本移交给自动化测试执行团队使用并维护,这种情况下脚本的可读性就至关重要了。但即使是同一拨人维护,一段时间后,当时的开发人员也会遗忘某些甚至是大部分的开发步骤。</li>
- **由于脚本的每一行都直接描述各个页面上的元素操作,你很难一眼看出脚本更高层的业务测试流程。** 比如图2的业务测试流程其实就三大步用户登录、搜索书籍和用户登出但是通过阅读代码很难一下看出来。
- **通用步骤会在大量测试脚本中重复出现。** 脚本中的某些操作集合在业务上是属于通用步骤比如上面伪代码的第1-4行完成的是用户登录操作第11-12行完成的是用户的登出操作。
这些通用的操作,会在其他测试用例的脚本中被多次重复。无论操作发生变动,还是页面控件的定位发生变化时,都需要同时修改大量的脚本。
其实我上面说到的这四点正是早期GUI自动化测试的主要问题这也是我一直说“开发几个GUI自动化测试玩玩会觉得很高效但是当你开发成百上千个GUI自动化测试的时候你会很痛苦”的本质含义。
那怎么解决这个问题呢?你可能已经想到了软件设计中模块化设计的思想。
**没错就是利用模块化思想把一些通用的操作集合打包成一个个名字有意义的函数然后GUI自动化脚本直接去调用这些操作函数来构成整个测试用例这样GUI自动化测试脚本就从原本的“流水账”过渡到了“可重用脚本片段”。**
如图3所示就是利用了模块化思想的伪代码。
<img src="https://static001.geekbang.org/resource/image/c9/33/c923bb048e0b33ff501e8b1b017b0333.png" alt="" />
第1-6行就是测试用例非常简单直接一眼就可以看出测试用例具体在执行什么操作而各个操作函数的具体内部实现还是之前那些“流水账”。当然这里对于测试输入数据完全可以采用测试驱动方法这里为了直观我就直接硬编码了测试示例数据。
实际工程应用中第1-6行的测试用例和第8-30行的操作函数通常不会放在一个文件中因为操作函数往往会被很多测试用例共享。这种模块化的设计思想带来的好处包括
<li>
**解决了脚本可读性差的问题,脚本的逻辑层次也更清晰了;**
</li>
<li>
**解决了通用步骤会在大量测试脚本中重复出现的问题,** 现在操作函数可以被多个测试用例共享,当某个步骤的操作或者界面控件发生变化时,只要一次性修改相关的操作函数就可以了,而不需要去每个测试用例中逐个修改。
</li>
但是这样的设计并没有完全解决早期GUI自动化测试的主要问题比如每个操作函数内部的脚本可读性问题依然存在而且还引入了新的问题即如何把控操作函数的粒度以及如何衔接两个操作函数之间的页面。
关于这两个新引入的问题我会在后面的文章中为你详细阐述。我先来跟你聊聊怎么解决早期GUI自动化测试的“可读性差、难以维护”问题。
现在,操作函数的内部实现还只是停留在“既有页面元素的定位查找,又有对元素的操作”的阶段,当业务操作本身比较复杂或者需要跨多个页面时,“可读性差、难以维护”的问题就会暴露得更加明显了。
那么有什么更好的办法来解决这个问题吗答案就是我要分享的GUI自动化测试的第二个概念页面对象Page Object模型。
页面对象模型的核心理念是以页面Web Page 或者Native App Page为单位来封装页面上的控件以及控件的部分操作。而测试用例更确切地说是操作函数基于页面封装对象来完成具体的界面操作最典型的模式是“XXXPage.YYYComponent.ZZZOperation”。
基于这个思想上述用例的伪代码可以进化成如图4所示的结构。这里我只给出了login函数的伪代码建议你按照这种思路自己去实现一下search和logout的代码这样可以帮你更好的体会页面对象模型带来的变化。
<img src="https://static001.geekbang.org/resource/image/45/1d/459a4eaeec085ca7c6083651052ab31d.png" alt="" />
通过这样的代码结构,你可以清楚地看到是在什么页面执行什么操作,代码的可读性以及可维护性大幅度提高,也可以更容易地将具体的测试步骤转换成测试脚本。
## 总结
今天我给你讲了什么是数据驱动的测试让你明白了“测试脚本和数据解耦”的实现方式以及应用场景。接着从GUI自动化测试历史发展演变的角度引出了GUI测试中的“页面对象模型”的概念。
“测试脚本和数据解耦”的本质是实现了数据驱动的测试,让操作相同但是数据不同的测试可以通过同一套自动化测试脚本来实现,只是在每次测试执行时提供不同的测试输入数据。
“页面对象模型”的核心理念是,以页面为单位来封装页面上的控件以及控件的部分操作。而测试用例使用页面对象来完成具体的界面操作。
希望这篇文章可以让你更清楚地认识GUI自动化测试用例的逻辑以及结构。同时你可能已经发现这篇文章的内容并不是局限在某个GUI自动化测试框架上你可以把这些设计思想灵活地运用其他GUI自动化测试项目中这也是我希望达到的“授人以鱼不如授人以渔”。
## 思考题
我在文中有这样一段描述:页面对象模型的核心理念是,以页面为单位来封装页面上的控件以及控件的部分操作。但是,现在业界对“是否应该在页面对象模型中封装控件的操作”一直有不同的看法。
有些观点认为,可以在页面对象模型中封装页面控件的操作;而有些观点则认为,页面对象模型只封装控件,而操作应该再做一层额外的封装。
你更认同哪种观点呢,说说你的理由吧。
欢迎你给我留言。

View File

@@ -0,0 +1,159 @@
<audio id="audio" title="14 | 更接近业务的抽象:让自动化测试脚本更好地描述业务" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ae/9d/aec932e7db396dc15fe0b595549f549d.mp3"></audio>
在上一篇文章中我介绍了GUI自动化测试中的两个主要的概念“脚本与数据的解耦 ”以及“ 页面对象模型”。在引入“操作函数”封装时,我提到操作函数在改善测试脚本可读性问题的同时,也引入了两个新的问题,即: 如何把控操作函数的粒度,以及如何衔接两个操作函数之间的页面。
现在我就以这两个问题作为引子为你介绍GUI自动化测试中“业务流程business flow”的概念、核心思想以及应用场景。
## 如何把控操作函数的粒度?
**操作函数的粒度是指,一个操作函数到底应该包含多少操作步骤才是最合适的。**
- 如果粒度太大,就会降低操作函数的可重用性。极端的例子就是,前面文章中涉及的百度搜索的案例,把“登录”“搜索”“登出”的操作作为一个操作函数。
- 如果粒度太小,也就失去了操作函数封装的意义。极端的例子就是,把每一个步骤都作为一个操作函数。
- 更糟糕的是,在企业实际自动化测试开发中,每个测试工程师对操作函数的粒度理解也不完全相同,很有可能出现同一个项目中脚本粒度差异过大,以及某些操作函数的可重用性低的问题。
**那么,操作函数的粒度到底应该如何控制呢?其实这个问题,在很大程度上取决于项目的实际情况,以及测试用例步骤的设计,并没有一个放之四海而皆准的绝对标准。**
但是脚本粒度的控制还是有设计依据可以遵循的即往往以完成一个业务流程business flow为主线抽象出其中的“高内聚低耦合”的操作步骤集合操作函数就由这些操作步骤集合构成。
比如,对于“用户注册”这个业务流程,其中的“信用卡绑定”操作就会涉及多个操作步骤,而这些操作在逻辑上又是相对独立的,所以就可以包装成一个操作函数。也就是说,业务流程会依次调用各个操作函数,来完成具体的业务操作。
## 如何衔接两个操作函数之间的页面?
完成一个业务流程操作,往往会需要依次调用多个操作函数,但是操作函数和操作函数之间会有页面衔接的问题,即前序操作函数完成后的最后一个页面,必须是后续操作函数的第一个页面。
如果连续的两个操作函数之间无法用页面衔接,那就需要在两个操作函数之间加入额外的页面跳转代码,或者是在操作函数内部加入特定的页面跳转代码。
## 业务流程抽象
在解决如何把控操作函数的粒度,以及如何衔接两个操作函数之间的页面这两个问题的过程中,我引入了业务流程的概念。那么,接下来我就跟你详细说说什么是业务流程。
**业务流程抽象是,基于操作函数的更接近于实际业务的更高层次的抽象方式。基于业务流程抽象实现的测试用例往往灵活性会非常好,你可以很方便地组装出各种测试用例。**
这个概念有点拗口,难以理解。但是,没关系,我举个例子,你就豁然开朗了。
假设某个具体的业务流程是已注册的用户登录电商平台购买指定的书籍。那么基于业务流程抽象的测试用例伪代码如图1所示。
<img src="https://static001.geekbang.org/resource/image/a7/46/a7b02e628552dd97070c90058b755a46.png" alt="" />
这段伪代码的信息量很大,但是理解了这段代码的设计思想,你也就掌握了业务流程抽象的精髓。
**首先从整体结构上看段伪代码顺序调用了4个业务流程** 依次是完成用户登录的LoginFlow、完成书籍查询的SearchBookFlow、完成书籍购买的CheckoutBookFlow、完成用户登出的LogoutFlow。
这4个业务流程都是作为独立的类封装的可以被很方便的重用并灵活组合类的内部实现通常是调用操作函数。而操作函数内部则是基于页面对象模型完成具体的页面控件操作。
**然后,对于每一个业务流程类,都会有相应的业务流程输入参数类与之一一对应。具体的步骤通常有这么几步:**
<li>
初始化一个业务流程输入参数类的实例;
</li>
<li>
给这个实例赋值;
</li>
<li>
用这个输入参数实例来初始化业务流程类的实例;
</li>
<li>
执行这个业务流程实例。
</li>
**执行业务流程实例的过程,其实就是调用操作函数来完成具体的页面对象操作的过程。**
为了让你更好地理解业务流程抽象提供了哪些功能,接下来我会为你逐行解读这段伪代码。
**伪代码的第2-6行调用的是LoginFlow完成了用户登录的操作。**
```
2: LoginFlowParameters loginFlowParameters = new LoginFlowParameters();
3: loginFlowParameters.setUserName(&quot;username&quot;);
4: loginFlowParameters.setPassword(&quot;password&quot;);
5: LoginFlow loginFlow = new LoginFlow(loginFlowParameters);
6: loginFlow.execute();
```
第2行初始化了LoginFlow对应的LoginFlowParameters的实例。
第3-4行通过setUserName和setPassword方法将用户名和密码传入该参数实例。
第5行用这个已经赋值的参数实例来初始化LoginFlow。
第6行通过execute方法发起执行。执行之后LoginFlow会调用内部的操作函数或者直接调用页面对象方法完成用户登录的操作。
**伪代码的第9-12行用和2-6行类似的方式调用了SearchBookFlow完成了书籍搜索的操作。**
```
9: SearchBookFlowParameters searchBookFlowParameters = new SearchBookFlowParameters();
10: searchBookFlowParameters.setBookName(&quot;bookname&quot;);
11: SearchBookFlow searchBookFlow = new SearchBookFlow(searchBookFlowParameters);
12: searchBookFlow.withStartPage(loginFlow.getEndPage()).execute();
```
需要特别注意的是第12行中withStartPage(loginFlow.getEndPage())的含义是SearchBookFlow的起始页面将会使用之前loginFlow的结束页面。显然通过这种方式可以很方便地完成两个业务流程之间的页面衔接。
同时从中还可以看出其实每个业务流程都可以接受不同的起始页面。以SearchBookFlow为例它的起始页面既可以是书籍首页也可以是其他页面但是需要在它的内部对不同的初始页面做出相应的处理以保证这个业务流程真正开始的页面是在书籍搜索页面。
同样由于业务流程存在分支的可能性每个业务流程执行完成的最终页面也不是唯一的你可以使用getEndPage方法拿到这个业务流程执行结束后的最后页面。
通过这段代码的解读,你可以很清楚地理解,业务流程之间的页面衔接是如何实现的。
**伪代码的第15-18行调用了CheckoutBookFlow完成了书籍购买操作。**
```
15: CheckoutBookFlowParameters checkoutBookFlowParameters = new CheckoutBookFlowParameters();
16: checkoutBookFlowParameters.setBookID(searchBookFlow.getOutPut().getBookID());
17: CheckoutBookFlow checkoutBookFlow = new CheckoutBookFlow(checkoutBookFlowParameters);
18: checkoutBookFlow.withStartPage(searchBookFlow.getEndPage()).execute();
```
第15行初始化了CheckoutBookFlow对应的checkoutBookFlowParameters的实例。
第16行通过setBookID(searchBookFlow.getOutPut().getBookID())将上一个业务流程searchBookFlow的输出参数作为了当前业务流程的输入参数。这是典型的业务流程之间如何传递参数的示例也是很多测试场景中都要用到的。
第17行用checkoutBookFlowParameters参数实例来初始化checkoutBookFlow。
第18行通过execute方法发起执行。这里需要注意的是checkoutBookFlow的起始页面将会使用之前searchBookFlow的结束页面。开始执行后checkoutBookFlow会调用内部的操作函数或者直接调用页面对象方法完成书籍的购买操作。
**伪代码的第21-22行调用LogoutFlow完成了用户登出操作。**
```
21: LogoutFlow logoutFlow = new LogoutFlow();
22: logoutFlow.withStartPage(checkoutBookFlow.getEndPage()).execute();
```
第21行由于LogoutFlow不带参数所以直接初始化了LogoutFlow。
第22行通过execute方法发起执行。这里LogoutFlow的起始页面将会使用之前CheckoutBookFlow的结束页面。开始执行后LogoutFlow会调用内部的操作函数或者直接调用页面对象方法完成用户登出操作。
通过对这些代码的解读,我解释了业务流程是什么,并从使用者的角度分析了它的主要特点。比如,如何实现不同业务流程间的页面衔接,如何在不同的业务流程间传递参数等。
为了加深印象,我再来总结一下业务流程的优点:
<li>
业务流程Business Flow的封装更接近实际业务
</li>
<li>
基于业务流程的测试用例非常标准化遵循“参数准备”、“实例化Flow”和“执行Flow”这三个大步骤非常适用于测试代码的自动生成
</li>
<li>
由于更接近实际业务所以可以很方便地和BDD结合。BDD就是Behavior Driven Development即行为驱动开发我会在后续文章中详细讲解。
</li>
## 总结
我以如何把控操作函数的粒度,和如何衔接两个操作函数之间的页面,这两个问题为引子,为你介绍了业务流程的概念、核心思想和适用的场景。
业务流程抽象是,基于操作函数的更接近于实际业务的更高层次的抽象方式。基于业务流程抽象实现的测试用例往往具有较好的灵活性,可以根据实际测试需求方便地组装出各种测试用例。
业务流程的核心思想是从业务的维度来指导测试业务流程的封装。由于业务流程封装通常很贴近实际业务所以特别适用于组装面向终端用户的端到端E2E的系统功能测试用例尤其适用于业务功能非常多并且存在各种组合的E2E测试场景。
## 思考题
你所在公司的GUI自动化测试是否已经运用了业务流程级别的封装在使用过程中你是否遇到什么瓶颈是如何解决的
欢迎你给我留言。

View File

@@ -0,0 +1,171 @@
<audio id="audio" title="15 | 过不了的坎聊聊GUI自动化过程中的测试数据" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f1/82/f12f4a9322e619b8e540a0d577a51482.mp3"></audio>
在前面几篇文章中我从页面操作的角度介绍了GUI自动化测试讲解了页面对象模型和业务流程封装今天我将从测试数据的角度再来谈谈GUI自动化测试。
为了顺利进行GUI测试往往需要准备测试数据来配合测试的进行如果不采用事先数据准备的方式测试效率将会大打折扣而且还会引入大量不必要的依赖关系。
以“用户登录”功能的测试为例如果你的目的仅仅是测试用户是否可以正常登录比较理想的方式是这个用户已经存在于被测系统中了或者你可以通过很方便的方式在测试用例中生成这个用户。否则难道你要为了测试用户登录功能而以GUI的方式当场注册一个新用户吗显然这是不可取的。
其实从这里你就可以看出测试数据准备是实现测试用例解耦的重要手段你完全不必为了测试GUI用户登录功能而去执行用户注册只要你能够有方法快速创建出这个登录用户就可以了。
在正式讨论测试数据的创建方法前我先来分析一下GUI测试中两种常见的数据类型
<li>
第一大类是测试输入数据也就是GUI测试过程中通过界面输入的数据。比如“用户登录”测试中输入的用户名和密码就就属于这一类数据再比如数据驱动测试中的测试数据也是指这一类。
</li>
<li>
第二大类是为了完成GUI测试而需要准备的测试数据。比如“用户登录”测试中我们需要事先准备好用户账户以便进行用户的登录测试。今天我分享的测试数据创建的方法也都是围着这一部分的数据展开的。
</li>
那么接下来,我就带你一起去看看创建测试数据的方法都有哪些,以及它们各自的优缺点,和适用场景。
从创建的技术手段上来讲,创建测试数据的方法主要分为三种:
<li>
API调用
</li>
<li>
数据库操作;
</li>
<li>
综合运用API调用和数据库操作。
</li>
从创建的时机来讲,创建测试数据的方法主要分为两种:
<li>
测试用例执行过程中实时创建测试数据我们通常称这种方式为On-the-fly。
</li>
<li>
测试用例执行前事先创建好“开箱即用”的测试数据我们通常称这种方式为Out-of-box。
</li>
**在实际项目中对于创建数据的技术手段而言最佳的选择是利用API来创建数据只有当API不能满足数据创建的需求时才会使用数据库操作的手段。**
实际上往往很多测试数据的创建是基于API和数据库操作两者的结合来完成即先通过API创建基本的数据然后调用数据库操作来修改数据以达到对测试数据的特定要求。
**而对于创建数据的时机在实际项目中往往是On-the-fly和Out-of-box结合在一起使用。**
对于相对稳定的测试数据比如商品类型、图书类型等往往采用Out-of-box的方式以提高效率而对于那些只能一次性使用的测试数据比如商品、订单、优惠券等往往采用On-the-fly的方式以保证不存在脏数据问题。
接下来,我就先从测试数据创建的技术手段开始今天的分享吧。
## 基于API调用创建测试数据
先看一个电商网站“新用户注册”的例子当用户通过GUI界面完成新用户注册信息填写后向系统后台递交表单系统后台就会调用createUser的API完成用户的创建。
而互联网产品尤其是现在大量采用微服务架构的网站这个API往往以Web Service的形式暴露接口。那么在这种架构下你完全可以直接调用这个API来创建新用户而无须再向后台递交表单。
由于API通常都有安全相关的token机制来保护所以实际项目中通常会把对这些API的调用以代码的形式封装为测试数据工具Test Data Utility
这种方式最大的好处就是测试数据的准确性直接由产品API保证缺点是并不是所有的测试数据都有相关的API来支持。
另外对需要大量创建数据的测试来说基于API调用方式的执行效率即使采用了并发机制也不会十分理想。为了解决执行效率的问题就有了基于数据库操作的测试数据创建手段。
## 基于数据库操作创建测试数据
实际项目中并不是所有的数据都可以通过API的方式实现创建和修改很多数据的创建和修改直接在产品代码内完成而且并没有对外暴露供测试使用的接口。
那么,这种情况下,你就需要通过直接操作数据库的方式来产生测试数据。
同样地我们可以把创建和修改数据的相关SQL语句封装成测试数据工具以方便测试用例的使用。但是如果你正尝试在实际项目中运用这个方法不可避免地会遇到如何才能找到正确的SQL语句来创建和修改数据的问题。
因为,创建或修改一条测试数据往往会涉及很多业务表,任何的遗漏都会造成测试数据的不准确,从而导致有些测试因为数据问题而无法进行。
那么,现在我就提供两个思路来帮你解决这个问题:
<li>
手工方式。查阅设计文档和产品代码找到相关的SQL语句集合。或者直接找开发人员索要相关的SQL语句集合。
</li>
<li>
自动方式。在测试环境中先在只有一个活跃用户的情况下通过GUI界面操作完成数据的创建、修改然后利用数据库监控工具获取这段时间内所有的业务表修改记录以此为依据开发SQL语句集。
</li>
需要注意的是,这两种思路的前提都是,假定产品功能正确,否则就会出现“一错到底”的尴尬局面。
基于数据库操作创建测试数据的最大好处是可以创建和修改API不支持的测试数据并且由于是直接数据库操作执行效率会远远高于API调用方法。
但是数据库操作这种方式的缺点也显而易见数据库表操作的任何变更都必须同步更新测试数据工具中的SQL语句。
但很不幸的是在实际项目中经常出现因为SQL语句更新不及时而导致测试数据错误的问题而且这里的数据不准确往往只是局部错误因此这类问题往往比较隐蔽只有在特定的测试场景下才会暴露。
所以在实际工程项目中需要引入测试数据工具的版本管理并通过开发流程来保证SQL的变更能够及时通知到测试数据工具团队。
## 综合运用API调用和数据库操作创建测试数据
你如果已经理解了基于API调用和基于数据库操作创建测试数据这两类方法那么综合运用这两类方法就是使得测试数据工具能够提供更多种类的业务测试数据。
具体来讲当你要创建一种特定的测试数据时你发现没有直接API支持但是可以通过API先创建一个基本的数据然后再通过修改数据库的方式来更新这个数据以此来达到创建特定测试数据的要求。
比如你需要创建一个已经绑定了信用卡的新用户如果创建新用户有直接的API而绑定信用卡需要操作数据库那这种情况下就需要综合运用这两种方式完成测试数据工具的开发。
## 实时创建数据On-the-fly
**GUI测试脚本中在开始执行界面操作前我们往往会通过调用测试数据工具实时创建测试数据也就是On-the-fly方式。**
这种方式不依赖被测试系统中的任何原有数据,也不会对原有数据产生影响,可以很好地从数据层面隔离测试用例,让测试用例实现“自包含”。
从理论上讲On-the-fly是很好的方法但在实际测试项目中却并不是那么回事儿往往会存在三个问题
<li>
**在用例执行过程中实时创建数据,导致测试的执行时间比较长。** 我曾经粗略统计过一个大型Web GUI自动化测试项目的执行时间将近30%的时间都花在了测试数据的准备上。
</li>
<li>
<p>**业务数据的连带关系,导致测试数据的创建效率非常低。** 比如,你需要创建一个订单数据,而这个订单必然会绑定买家和卖家,以及订单商品信息。<br />
如果完全基于On-the-fly模式你就需要先实时创建买家和卖家这两个用户然后再创建订单中的商品最后才是创建这个订单本身。<br />
显然,这样的测试数据创建方式虽然是“自包含”的,但创建效率非常低,会使得测试用例执行时间变得更长,而这恰恰与互联网产品的测试策略产生冲突。</p>
</li>
<li>
<p>**更糟糕的情况是,实时创建测试数据的方式对测试环境的依赖性很强。** 比如你要测试用户登录功能基于On-the-fly方式你就应该先调用测试数据工具实时创建一个用户然后再用这个用户完成登录测试。<br />
这时创建用户的API由于各种原因处于不可用的状态这种情况在采用微服务架构的系统中很常见那么这时就会因为无法创建用户而无法完成用户登录测试。</p>
</li>
基于这三种常见问题实际项目中还会引入Out-of-box方式即在执行测试用例前预先创建好测试数据准备测试数据。
## 事先创建测试数据Out-of-box
**Out-of-box的含义是开箱即用也就是说已经在被测系统中预先创建好了充足的、典型的测试数据。这些数据通常是在搭建测试环境时通过数据库脚本“预埋”在系统中的后续的测试用例可以直接使用。**
Out-of-box的方式有效解决了On-the-fly的很多问题但是这种方法的缺点也很明显主要体现在以下三个方面
<li>
**测试用例中需要硬编码hardcode测试数据额外引入了测试数据和用例之间的依赖。**
</li>
<li>
**只能被一次性使用的测试数据不适合Out-of-box的方式。** 测试用例往往会需要修改测试数据,而且有些测试数据只能被一次性使用。比如,一个商品被买下一次后就不能再用了;再比如,优惠券在一个订单中被使用后,就失效了,等等。所以如果没有很好的全局测试数据管理,很容易因为测试数据失效而造成测试失败。
</li>
<li>
**“预埋”的测试数据的可靠性远不如实时创建的数据。** 在测试用例执行过程中,经常会出现测试数据被修改的情况。比如,手动测试,或者是自动化测试用例的调试等情况。
</li>
## On-the-fly和Out-of-box的互补
基于On-the-fly和Out-of-box的优缺点和互补性在实际的大型测试项目中我们往往会采用两者相结合的方式从测试数据本身的特点入手选取不同的测试数据创建方式。
针对应该选择什么时机创建测试数据,结合多年的实践经验,我为你总结了以下三点:
<li>
对于相对稳定、很少有修改的数据建议采用Out-of-box的方式比如商品类目、厂商品牌、部分标准的卖家和买家账号等。
</li>
<li>
对于一次性使用、经常需要修改、状态经常变化的数据建议使用On-the-fly的方式。
</li>
<li>
用On-the-fly方式创建测试数据时上游数据的创建可以采用Out-of-box方式以提高测试数据创建的效率。以订单数据为例订单的创建可以采用On-the-fly方式而与订单相关联的卖家、买家和商品信息可以使用Out-of-box方式创建。
</li>
其实,为了更好地解决测试数据本身组合的复杂性和多样性,充分发挥测试数据工具的威力,还有很多大型企业的最佳实践值得讨论,在本专栏后面的测试数据章节,我会再为你详细介绍。
## 总结
今天我从创建测试数据的技术手段和时机两个方面介绍了GUI测试数据的准备。
在实际测试项目中往往需要综合运用API调用和数据库操作来创建测试数据并且会根据测试数据自身的特点分而治之地采用On-the-fly和Out-of-box的方式以寻求数据稳定性和数据准备效率之间的最佳平衡。
## 思考题
你所在的公司是如何准备GUI测试的测试数据的遇到了哪些问题对应的有哪些解决方案呢
欢迎你给我留言。

View File

@@ -0,0 +1,112 @@
<audio id="audio" title="16 | 脑洞大开GUI测试还能这么玩Page Code Gen + Data Gen + Headless" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a3/72/a39e4c8b47ac41e4a3ca566cdecc2d72.mp3"></audio>
在前面的几篇文章中我介绍了GUI自动化测试的数据驱动测试、页面对象Page Object模型、业务流程封装以及测试数据相关的内容。
今天这篇文章我将从页面对象自动生成、GUI测试数据自动生成、无头浏览器三个方面展开这也是GUI测试中三个比较有意思的知识点。
## 页面对象自动生成
在前面的文章中我已经介绍过页面对象Page Object模型的概念。页面对象模型是以Web页面为单位来封装页面上的控件以及控件的部分操作而测试用例基于页面对象完成具体操作。最典型的模式就是XXXPage.YYYComponent.ZZZOperation。
基于页面对象模型的伪代码示例如图1所示。
<img src="https://static001.geekbang.org/resource/image/8f/df/8f49888b1fbae32994f3e4f8c5e77adf.png" alt="" />
如果你在实际项目中已经用过页面对象模型你会发现开发和维护页面对象的类Page Class是一件很耗费时间和体力的事儿。
- 你需要打开页面识别出可以唯一确定某元素的属性或者属性集合然后把它们写到Page Class里比如图1的第2行代码username_input=findElementByName(“username”)就是通过控件的名字username来定位元素的。
- 更糟糕的是GUI的页面会经常变动如果开发人员开发前端代码时没有严格遵循可测试性的要求Page Class的维护成本就会更高。
那么什么方法能够解决这个问题呢答案就是页面对象自动生成技术它非常适用于需要维护大量页面对象的中大型GUI自动化测试项目。
页面对象自动生成技术属于典型的“自动化你的自动化”的应用场景。它的基本思路是你不用再手工维护Page Class了只需要提供Web的URL它就会自动帮你生成这个页面上所有控件的定位信息并自动生成Page Class。
**但是需要注意的是那些依赖于数据的动态页面对象也会被包含在自动生成的Page Class里而这种动态页面对象通常不应该包含在Page Class里所以往往需要以手工的方式删除。**
目前很多商用自动化工具比如UFT已经支持页面对象自动生成功能了同时还能够对Page Class进行版本管理。
但是,开源的自动化方案,页面对象自动生成功能一般需要自己开发,并且需要与你所用的自动化测试框架深度绑定。目前,中小企业很少有自己去实现这一功能的。
不过有个好消息是目前国内应用还不算多、免费的Katalon Studio已经提供了类似的页面对象库管理功能如果感兴趣的话你可以去试用一下。
## GUI测试数据自动生成
GUI测试数据自动生成指的由机器自动生成测试用例的输入数据。
乍一听上去是不是感觉有点玄乎?机器不可能理解你的业务逻辑,怎么可能自动生成测试数据呢?
你的这个想法完全合理,并且也是完全正确的。所以,我在这里说的“测试数据自动生成”,仅仅局限于以下两种情况:
<li>
<p>**根据GUI输入数据类型以及对应的自定义规则库自动生成测试输入数据。** 比如GUI界面上有一个“书名”输入框它的数据类型是string。<br />
那么,基于数据类型就可以自动生成诸如 Null、SQL注入、超长字符串、非英语字符等测试数据。<br />
同时,根据自定义规则库,还可以根据具体规则生成各种测试数据。这个自定义规则库里面的规则,往往反映了具体的业务逻辑。比如,对于“书名”,就会有书名不能大于多少个字符、一些典型的书名(比如,英文书名、中文书名等)等等业务方面的要求,那么就可以根据这些业务要求来生成测试数据。<br />
根据自定义规则生成测试数据的核心思想与安全扫描软件AppScan基于攻击规则库自动生成和执行安全测试的方式有异曲同工之处。</p>
</li>
<li>
<p>**对于需要组合多个测试输入数据的场景,测试数据自动生成可以自动完成多个测试数据的笛卡尔积组合,然后再以人工的方式剔除掉非法的数据组合。**<br />
但是,这种方式并不一定是最高效的。对于输入参数比较多,且数据之间合法组合比较少或者难以明确的情况,先自动化生成笛卡尔积组合,再删除非法组合,效率往往还不如人为组合来得高。所以,在这个场景下是否要用测试数据自动生成方法,还需要具体问题具体分析。<br />
更常见的用法是,先手动选择部分输入数据进行笛卡尔积,并删除不合法的部分;然后,在此基础上,再人为添加更多业务上有意义的输入数据组合。<br />
比如输入数据有A、B、C、D、E、F六个参数你可以先选取最典型的几个参数生成笛卡尔积假设这里选取A、B和C然后在生成的笛卡尔积中删除业务上不合法的组合最后再结合D、E和F的一些典型取值构成更多的测试输入数据组合。</p>
</li>
## 无头浏览器
无头浏览器即Headless Browser是一种没有界面的浏览器。
什么?浏览器没有界面,还叫什么浏览器啊?别急,我将为你一一道来。
无头浏览器其实是一个特殊的浏览器你可以把它简单地想象成是运行在内存中的浏览器。它拥有完整的浏览器内核包括JavaScript解析引擎、渲染引擎等。
与普通浏览器最大的不同是无头浏览器执行过程中看不到运行的界面但是你依然可以用GUI测试框架的截图功能截取它执行中的页面。
无头浏览器的主要应用场景包括GUI自动化测试、页面监控以及网络爬虫这三种。在GUI测试过程中使用无头浏览器的好处主要体现在四个方面
<li>
**测试执行速度更快。** 相对于普通浏览器来说无头浏览器无需加载CSS以及渲染页面在测试用例的执行速度上有很大的优势。
</li>
<li>
**减少对测试执行的干扰。** 可以减少操作系统以及其他软件(比如杀毒软件等)不可预期的弹出框,对浏览器测试的干扰。
</li>
<li>
**简化测试执行环境的搭建。** 对于大量测试用例的执行而言可以减少对大规模Selenium Grid集群的依赖GUI测试可以直接运行在无界面的服务器上。
</li>
<li>
**在单机环境实现测试的并发执行。** 可以在单机上很方便地运行多个无头浏览器,实现测试用例的并发执行。
</li>
但是,**无头浏览器并不完美,它最大的缺点是,不能完全模拟真实的用户行为,而且由于没有实际完成页面的渲染,所以不太适用于需要对于页面布局进行验证的场景。同时,业界也一直缺乏理想的无头浏览器方案。**
在Google发布Headless Chrome之前PhantomJS是业界主流的无头浏览器解决方案。但是这个项目的维护一直以来做得都不够好已知未解决的缺陷数量多达1800多个虽然支持主流的Webkit浏览器内核但是依赖的Chrome版本太低。所以无头浏览器一直难以在GUI自动化测试中大规模应用。
但好消息是2017年Google发布了Headless Chrome以及与之配套的Puppeteer框架Puppeteer不仅支持最新版本的Chrome而且得到Google官方的支持这使得无头浏览器可以在实际项目中得到更好的应用。
也正是这个原因PhantomJS的创建者Ariya Hidayat停止了它的后续维护Headless Chrome成了无头浏览器的首选方案。
那什么是Puppeteer呢Puppeteer是一个Node库提供了高级别的API封装这些API会通过Chrome DevTools Protocol与Headless Chrome的交互达到自动化操作的目的。
Puppeteer也是由Google开发的所以它可以很好地支持Headless Chrome以及后续Chrome的版本更新。
如果你也迫不及待地想要尝试把Headless Chrome应用到自己的GUI测试中那还等什么赶紧下载并开始吧。
## 总结
我分别介绍了无头浏览器、页面对象自动生成以及GUI测试数据自动生成这三个GUI测试中比较有意思的知识点包括它们的概念、应用场景等内容。
<li>
对于页面对象自动生成,商用测试软件已经实现了这个功能。但是,如果你选择开源测试框架,就需要自己实现这个功能了。
</li>
<li>
GUI测试数据自动生成主要是基于测试输入数据的类型以及对应的自定义规则库实现的并且对于多个测试输入数据可以基于笛卡尔积来自动组合出完整的测试用例集合。
</li>
<li>
对于无头浏览器你可以把它简单地想象成运行在内存中的浏览器它拥有完整的浏览器内核。与普通浏览器最大的不同是它在执行过程中看不到运行的界面。目前Headless Chrome结合Puppeteer是最先进的无头浏览器方案如果感兴趣你可以下载试用。
</li>
## 思考题
在你的工作中还有哪些好的方法和实践可以提高GUI自动化测试的效率吗
欢迎你给我留言。

View File

@@ -0,0 +1,151 @@
<audio id="audio" title="17 | 精益求精聊聊提高GUI测试稳定性的关键技术" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/32/d0/32469fefe3ca09d4b5132c0bba4c67d0.mp3"></audio>
不知不觉我已经介绍完了GUI测试相关的知识点你可以先回顾一下这些知识点是否还有不清楚的地方也欢迎你给我留言进行讨论。同时我希望这些知识点已经帮你搭建了GUI自动化测试的知识体系。
那么今天我将从实际工程应用的角度和你一起聊聊GUI测试的稳定性问题。
如果你所在的公司已经规模化地开展了GUI测试那我相信你们也一定遇到过测试稳定性的问题。**GUI自动化测试稳定性最典型的表现形式就是同样的测试用例在同样的环境上时而测试通过时而测试失败。** 这也是影响GUI测试健康发展的一个重要障碍严重降低了GUI测试的可信性。
所以今天我分享的主题就是如何提高GUI测试的稳定性。虽然从理论上来讲GUI测试有可能做到100%稳定但在实际项目中这是一个几乎无法达到的目标。根据我的经验如果能够做到95%以上的稳定性,就已经非常不错了。
要提高GUI测试稳定性首先你需要知道到底是什么原因引起的不稳定。你必须找出尽可能多的不稳定因素然后找到每一类不稳定因素对应的解决方案。
为此根据我的实践经验以及所遇到的场景我为你总结了五种造成GUI测试不稳定的因素
<li>
非预计的弹出对话框;
</li>
<li>
页面控件属性的细微变化;
</li>
<li>
被测系统的A/B测试
</li>
<li>
随机的页面延迟造成控件识别失败;
</li>
<li>
测试数据问题。
</li>
并且,我提供了针对这五种不稳定因素的解决思路。
## 非预计的弹出对话框
非预计的弹出对话框,一般包含两种场景;
<li>
<p>**GUI自动化测试用例执行过程中操作系统弹出的非预计对话框** 有可能会干扰GUI测试的自动化执行。<br />
比如GUI测试运行到一半操作系统突然弹出杀毒软件更新请求、病毒告警信息、系统更新请求等对话框。这种对话框的弹出往往是难以预计的但是一旦发生就有可能造成GUI自动化测试的不稳定。</p>
</li>
<li>
<p>**被测软件本身也有可能在非预期的时间弹出预期的对话框,** GUI自动化测试有可能会因此而失败。<br />
比如被测软件是一个电子商务网站你在网站上进行操作时很可能会随机弹出“用户调查”对话框。虽然这种对话框是可知的但是具体会在哪一步弹出却是不可预期的。而这往往会造成GUI自动化测试的不稳定。</p>
</li>
怎么解决这类问题呢?
先试想一下,如果你在手工测试时,遇到了这种情况,会如何处理?很简单啊,直接点击对话框上的“确认”或者“取消”按钮,关闭对话框,然后继续相关的业务测试操作。
对GUI自动化测试来说也是同样的道理。具体做法是
- 当自动化脚本发现控件无法正常定位或者无法操作时GUI自动化框架自动进入“异常场景恢复模式”。
- 在“异常场景恢复模式”下GUI自动化框架依次检查各种可能出现的对话框一旦确认了对话框的类型立即执行预定义的操作比如单击“确定”按钮关闭这个对话框接着重试刚才失败的步骤。
需要注意的是:这种方式只能处理已知可能出现的对话框。而对于新类型的对话框,只能通过自动化的方式尝试点击上面的按钮进行处理。每当发现一种潜在会弹出的对话框,我们就把它的详细信息(包括对象定位信息等)更新到“异常场景恢复”库中,下次再遇到相同类型的对话框时,系统就可以自动关闭了。
## 页面控件属性的细微变化
如果页面控件的属性发生了变化,哪怕只是细微的变化,也会导致测试脚本的定位元素失效。
比如“登录”按钮的ID从“Button_Login_001”变成了“Button_Login_888”那么如果GUI自动化测试脚本还是按照原来的“Button_Login_001”来定位“登录”按钮就会因为ID值的变化定位不到它了自动化测试用例自然就会失败。
如何解决这个问题呢?还是先试想一下,如果手动操作时遇到了这个问题会怎么处理,然后再把手动处理的方式用编程语言实现。
当“登录”按钮的ID 从“Button_Login_001”变成了 “Button_Login_888”你手动操作时可能一眼就发现了。那你是怎么做到一眼发现的呢
细想一下,你会发现人的思维过程应该是这样的:
>
你发现页面上的按钮Button就那么几个而且从ID中包含的关键字Login可以看出是“登录”按钮再加上这个按钮的ID是“Button_Login_001”“Button_Login_888”怎么看都是同一个对象只是ID最后的数字发生了变化而已。
现在,我来提炼一下这个定位控件的思路:
<li>
通过控件类型Button缩小了范围
</li>
<li>
通过属性值中的关键字Login进一步缩小范围
</li>
<li>
根据属性值变化前后的相似性,最终定位到该控件。
</li>
看到这里,你得到什么启发了吗?
采用“组合属性”定位控件会更精准,而且成功率会更高,如果能在此基础上加入“模糊匹配”技术,可以进一步提高控件的识别率。
“模糊匹配”是指,通过特定的相似度算法,控件属性发生细微变化时,这个控件依旧可以被准确定位。
目前一些商用GUI自动化测试工具比如UFT已经实现了模糊匹配。通常情况下你只需要启用“模糊匹配”选项即可。如果某个对象的定位是通过模糊匹配完成的那么测试报告中将会显示该信息明确告知此次对象识别是基于模糊匹配完成的因为GUI自动化工具并不能保证每次模糊匹配都一定正确。
但是开源的GUI自动化测试框架目前还没有现成的框架直接支持模糊匹配通常需要你进行二次开发实现思路是实现自己的对象识别控制层也就是在原本的对象识别基础上额外封装一层在这个额外封装的层中加上模糊匹配的实现逻辑。
通常,我不建议把模糊匹配逻辑以硬编码的方式写在代码里,而是引入规则引擎,将具体的规则通过配置文件的方式与代码逻辑解耦。
## 被测系统的A/B测试
A/B测试是互联网产品常用的一种测试方法。它为Web或App的界面或流程提供两个不同的版本然后让用户随机访问其中一个版本并收集两个版本的用户体验数据和业务数据最后分析评估出最好的版本用于正式发布。
A/B 测试通常会发布到实际生产环境所以就会造成生产环境中GUI自动化测试的不稳定。
这种问题的解决思路是在测试脚本内部对不同的被测版本做分支处理脚本需要能够区分A和B两个的不同版本并做出相应的处理。
## 随机的页面延迟造成控件识别失败
随机的页面延迟也是GUI测试防不胜防的。既然是随机的也就是说我们没有办法去控制它那有没有什么办法去减少它造成的影响呢
一个屡试不爽的办法就是加入重试retry机制。重试机制是指当某一步GUI操作失败时框架会自动发起重试重试可以是步骤级别的也可以是页面级别的甚至是业务流程级别的。
对于开源GUI测试框架重试机制往往不是自带的功能需要自己二次开发来实现。
比如eBay的GUI自动化测试框架分别实现了步骤级别、页面级别和业务流程级别的重试机制默认情况下启用的是步骤级别的重试页面级别和业务流程级别的重试可以通过测试发起时的命令行参数进行指定。
需要特别注意的是,对于那些会修改一次性使用数据的场景,切忌不要盲目启用页面级别和业务流程级别的重试。
## 测试数据问题
测试数据问题也是造成GUI自动化测试不稳定的一个重要原因。
比如,测试用例所依赖的数据被其他用例修改了;再比如,测试过程中发生错误后自动进行了重试操作,但是数据状态已经在第一次执行中被修改了。
这样的场景还有很多,我会在后面的测试数据准备系列文章中详细展开,并分析由此引入的测试不稳定性问题的解决思路。
## 总结
根据我的实践经验我归纳了五种造成GUI自动化测试不稳定的主要因素并给出了对应的解决思路。
<li>
对于非预计的弹出对话框引起的不稳定,可以引入“异常场景恢复模式”来解决。
</li>
<li>
对于页面控件属性的细微变化造成的不稳定,可以使用“组合属性”定位控件,并且可以通过“模糊匹配技术”提高定位识别率。
</li>
<li>
对于A/B测试带来的不稳定需要在测试用例脚本中做分支处理并且需要脚本做到正确识别出不同的分支。
</li>
<li>
对于随机的页面延迟造成的不稳定,可以引入重试机制,重试可以是步骤级别的,也可以是页面级别的,甚至是业务流程级别的。
</li>
<li>
对于测试数据引起的不稳定,我在这里没有详细展开,留到后续的测试数据准备系列文章中做专门介绍。
</li>
## 思考题
在工作中你还遇到过哪些造成GUI测试不稳定的因素你又是如何来解决的
欢迎你给我留言。

View File

@@ -0,0 +1,137 @@
<audio id="audio" title="18 | 眼前一亮带你玩转GUI自动化的测试报告" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/87/ee/872ca4ed0155da2412eaf178d01255ee.mp3"></audio>
在GUI自动化测试系列的文章中我围绕着GUI自动化测试进行了各种讨论从最原始的GUI测试谈起逐渐引入了脚本与数据的解耦并谈论了页面对象模型以及在此基础上的业务流程模型接着分享了一些GUI自动化测试过程中的新技术最后和你讨论了GUI自动化测试的稳定性问题。
今天我会再和你聊聊GUI自动化测试过程中另外一个很实用的部分GUI自动化测试报告。
GUI测试报告是GUI自动化测试的重要组成部分当有任何的测试用例执行失败时我们首先就会去分析测试报告希望从中看到测试用例到底是在哪一步出错了错误发生时被测系统是在哪个页面上并且前序步骤又是哪些页面等等。
## 早期的基于视频的GUI测试报告
为了分析测试用例的执行过程与结果早期就出现了基于视频的GUI测试报告。也就是说GUI自动化测试框架会对测试执行整个过程进行屏幕录像并生成视频。
这种基于视频的测试报告可以提供清晰的GUI测试执行上下文看起来也很不错。但是这种方式主要的问题是
<li>
报告的体积通常都比较大小的几MB大的上百MB这对测试报告的管理和实时传输非常不利。
</li>
<li>
分析测试报告时,往往需要结合测试用例以及服务端的日志信息,视频报告这一点上也有所欠缺。
</li>
所以理想中的GUI测试报告应该是由一系列按时间顺序排列的屏幕截图组成并且这些截图上可以高亮显示所操作的元素同时按照执行顺序配有相关操作步骤的详细描述。
但是早期的商业GUI自动化测试软件也只是具备最基本的顺序截图并不具备高亮所操作元素的功能后来商用工具厂商根据用户的实际使用反馈逐渐完善和改进。
目前商业的GUI自动化测试软件比如使用最为广泛的UFT就是以前的QTP已经自带了截图以及高亮显示操作元素功能。也就是说使用UFT执行一个GUI自动化测试用例你无需做任何额外的工作就能得到一份比较理想的GUI测试报告。
## 开源GUI测试框架的测试报告实现思路
但是如果你使用的是开源软件比如Selenium WebDriver那就需要自己去实现截图以及高亮显示操作元素的功能。实现的思路通常是
>
利用Selenium WebDriver的screenshot函数在一些特定的时机比如页面发生跳转时在页面上操作某个控件时或者是测试失败时等等完成界面截图功能。
具体到代码实现,通常有两种方式:
<li>
扩展Selenium原本的操作函数
</li>
<li>
在相关的Hook操作中调用screenshot函数。
</li>
下面,我会分别针对这两个实现方式,给出具体的示例,帮你理解并实现这个功能。
**第一扩展Selenium原本的操作函数实现截图以及高亮显示操作元素的功能**
既然Selenium原生的click操作函数并不具备截图以及高亮显示操作元素的功能那我们就来实现一个自己click函数。
当自己实现的click函数被调用时
- 首先用Javascript代码高亮显示被操作的元素高亮的实现方式就是利用JavaScript在对象的边框上渲染一个5-8个像素的边缘
- 然后调用screenshot函数完成点击前的截图
- 最后调用Selenium原生的click函数完成真正的点击操作。
那么以后凡是需要调用click函数时都直接调用这个自己封装的click函数直接得到高亮了被操作对象的界面截图。
如图1所示就是用这种方式产生的界面截图图中依次显示了登录过程中每一个操作的控件第一张高亮了“Username”的输入框因为自动化代码会在“Username”框中输入用户名第二张高亮了“Password”的输入框因为自动化代码会在“Password”框中输入密码第三张高亮了”Sign in“按钮因为自动化代码会去点击这个按钮。
<img src="https://static001.geekbang.org/resource/image/cd/67/cd6e86a73dc2a13c285f0c94dbe2e367.png" alt="" />
**第二在相关的Hook操作中调用screenshot函数实现截图以及高亮显示操作元素的功能**
其实使用Hook的方法比较简单和直观但是你首先要理解什么是Hook。
Hook中文的意思是“钩子”直接通过定义介绍什么是“钩子”会有些难以理解那么我就通过一个实例来跟你解释一下。
当执行某个函数F时系统会在执行函数F前先隐式执行一个空实现的函数那么当你需要做一些扩展或者拦截时就可以在这个空实现的函数中加入自定义的操作了。那么这个空实现的函数就是所谓的Hook函数。
这样的例子有很多比如Java的main函数系统在执行main函数之前会先在后台隐式执行premain函数JUnit和TestNG都有所谓的BeforeTest和AfterTest方法这些都是可以在特定步骤的前后插入自定义操作的接口。
说到这里你可能已经知道要怎么做了我可以在这些Hook函数中添加截图、元素高亮以及额外的任意操作比如更多的详细日志输出等等。
另外我在前面的文章中分享了基于业务流程的脚本封装你可以再思考一下如何在GUI报告中体现出业务流程的概念这样的测试报告会具有更好的可读性。
比如图2所示的GUI测试报告就显示了具体的Flow名称。这个功能就是通过Hook函数实现的。
具体的实现逻辑也比较简单的就是在Flow开始的第一个Hook函数中调用增加报告页的函数并在这个新增的报告页中输出Flow的名字。
<img src="https://static001.geekbang.org/resource/image/f3/81/f335cb50728cb59ba1597697271d0081.png" alt="" />
上面所讲的GUI测试报告都是针对一个国家的当面对多个国家站点的GUI测试时事情就会变得更加复杂你就必须去考虑全球化GUI测试报告应该如何设计。
## 全球化GUI测试报告的创新设计
所谓全球化测试是指,同一个业务在全球各个国家都有自己网站。比如,一些大型全球化电商企业在很多国家都有自己的站点,那么对这些站点的测试除了要关注基本的功能,以及各个国家特有的功能外,还要去验证界面布局以及翻译在上下文环境中是否合适。
早期的做法是雇佣当地的测试工程师由他们手工执行主要的业务场景测试并验证相关的页面布局以及翻译内容与上下文中的匹配度。在当地专门雇佣的这些测试工程师被称为LQA。
显然聘请LQA的效率非常低主要原因是全部测试工作都由LQA在项目后期手工执行执行前还需要对他们进行业务培训同时我们需要准备非常详尽的测试用例文档LQA也要花很大的精力去截图并完成最终的测试报告。
为了解决这种低效的模式最好的解决方法就是利用GUI自动化测试工具生成完整的测试执行过程的截图。这样LQA就不再需要去手工执行测试用例了而是直接分析测试报告中业务操作过程中GUI界面截图就可以了然后发现页面布局问题或者是不恰当的翻译问题。
这个方案看起来已经比较完美了LQA的工作重点也更清晰了但这并不是最优的方案。因为这些LQA在实际工作中还会有以下三个比较痛苦的地方
<li>
需要经常在多个国家的测试报告之间来回切换去比较页面布局;
</li>
<li>
需要频繁切换到美国网站(也就是主站)的报告,去比较翻译内容与上下文的匹配度;
</li>
<li>
发现缺陷后还是需要从GUI测试报告中复制截图并用图像软件标注有问题的点然后才能打开缺陷管理系统递交缺陷报告。
</li>
为了解决这三个问题我建议你建立以下形式的测试报告。这里有一张图片展示了一份包含多国语言比较报告的示例听音频的用户可以点击文稿查看如图3所示。
<img src="https://static001.geekbang.org/resource/image/39/19/393c6ae5fe669e5513da9a2f7588af19.png" alt="" />
报告的横向,是一个国家的业务测试顺序截图,比如图中第一行是英国网站的登录业务流程顺序截图,第二行是德国网站的登录业务流程顺序截图。报告的纵向,展示的自然就是同一界面在不同国家的形式了。
整个报告可以用键盘上下左右依次移动。可想而知这样的GUI测试报告设计一定可以大幅提高LQA的效率。
同时由于这个GUI测试报告是基于Web展现的所以我们可以在测试报告中直接提供递交缺陷的按钮一旦发现问题直接递交缺陷同时还可以把相关截图一起直接递交到缺陷管理系统这将更大程度地提高整体效率。
那么怎么才能在技术上实现测试报告和缺陷管理系统的交互呢其实现今的缺陷管理系统往往都有对外暴露API接口我们完全可以利用这些API接口来实现自己的缺陷递交逻辑。
这种测试报告的形式就是eBay在全球化站点测试中采用的方案目前已经取得了很好地效果降低了工作量的同时还大幅度提高了全球化测试的质量。
## 总结
好了,希望上面的测试报告设计方法可以让你有眼前一亮的感觉。接下来,我总结一下今天的主要知识点。
早期基于视频的GUI测试报告由于体积较大而且不能比较方便地和日志适配所以并不是最好的解决方案。理想的GUI测试报告应该是由一系列按时间顺序的屏幕截图组成并且可以在这些截图上高亮你所操作的元素同时按照执行时序配有相关操作步骤的详细描述。
商业GUI自动化测试框架的GUI测试报告已经做得非常成熟通常不需要做额外的定制或者开发。但是开源GUI自动化测试框架的GUI测试报告往往需要自己来开发主要使用了扩展Selenium原本的操作函数的方式以及Hook函数来实现。
最后我介绍了eBay面对全球化测试过程中GUI测试报告的创新设计希望你也可以借鉴这种方法。
## 思考题
如果自己去开发GUI测试报告的功能你还能想到其他更多实用的功能吗你又是如何实现这些功能的
欢迎你给我留言。

View File

@@ -0,0 +1,130 @@
<audio id="audio" title="19 | 真实的战场如何在大型项目中设计GUI自动化测试策略" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2d/6e/2dc604d723e176440631e96c1b85006e.mp3"></audio>
在前面的文章中我介绍过GUI自动化测试的页面对象模型和业务流程封装等相关知识也提到过大型全球化电商网站的GUI自动化测试那如何把已经学到的GUI测试理论知识用到大型全球化电商网站的测试中呢
今天我的分享就从“实战”这个角度展开带你看看实际的大型全球化电商网站的GUI自动化测试如何开展。这场实战我将从以下两个方面展开
<li>
测试策略如何设计这一点我会根据亲身经历的实际项目和你探讨GUI测试的分层测试策略。
</li>
<li>
测试用例脚本如何组织需要注意的是对于这个问题我不是要和你讨论测试用例的管理而是要讨论测试用脚本的管理。比如当需要组装上层的端到端E2E测试时如何才能最大程度地重用已有的页面对象以及业务流程business flow
</li>
如果你所在的企业或者项目正在大规模开展GUI测试并且准备使用页面对象模型以及业务流程封装等最佳实践的话那么你很可能会遇到本文所描述的问题并且迫切需要相应的解决办法。
## 大型全球化电商网站的前端模块划分
在正式讨论大型全球化电商网站的GUI自动化测试策略设计之前我先简单介绍一下电商网站的前端架构为避免过多的技术细节引起不必要的干扰我只会概要性地介绍与GUI自动化测试密切相关的部分。
由于大型全球化电商网站的业务极其庞大,所以前端架构也要按照不同的业务模块来划分,比如用户管理模块、商户订单管理模块、商户商品管理模块等等。
当然由于这些前端模块都会使用项目自己封装的组件库,比如自定义开发的列表组件、登录组件、信用卡组件等,我们通常会把自定义开发的这些所有组件都放在一个“公共组件库”中,为前端模块提供依赖。
所以从代码库Repository的角度来看各个前端模块都有各自独立的代码库除此之外还会有一个公共组件的代码库。
## 大型全球化电商网站的GUI自动化测试策略设计
了解了大型全球化电商网站前端模块的划分后我们再来看看它的GUI自动化测试策略是如何设计的。
总体来看对大型网站来讲GUI自动化测试往往应该做得比较轻量级而不应该把大量的功能测试以及功能的组合测试放在GUI自动化测试中正如我在第11篇文章[《互联网产品的测试策略应该如何设计?》](https://time.geekbang.org/column/article/11462)中谈到的GUI测试通常只覆盖最核心且直接影响主营业务流程的E2E场景。
但同时GUI的验证一定不是在系统全部完成后才真正开展的也应该是分阶段、分层次来设计制定测试策略的那么接下来我也将会按照自底向上的顺序分层次介绍GUI自动化的测试策略。
首先,要从前端组件的级别来保证质量,也就是需要对那些自定义开发的组件进行完整全面的测试。
公共组件库会被很多上层的前端模块依赖它的质量将直接影响这些上层模块的质量所以我们往往会对这些公共组件进行严格的单元测试。最常用的方案是基于Jest开展单元测试并考量JavaScript的代码覆盖率指标。
Jest是由Facebook发布的是一个基于Jasmine的开源JavaScript单元测试框架是目前主流的JavaScript单元测试方案。
完成单元测试后,往往还会基于被测控件构建专用的测试页面,在页面层面再次验证控件相关的功能和状态。这部分测试工作也需要采用自动化的形式实现,具体的做法是:
<li>
先构建一个空页面并加入被测控件由此可以构建出一个包含被测控件的测试页面这个页面往往被称为Dummy Page
</li>
<li>
从黑盒的角度出发,在这个测试页面上通过手工和自动化的方式操作被测控件,并验证其功能的正确性。
</li>
对于自动化的部分需要基于GUI自动化测试框架开发对应的测试用例。这些测试用例往往采用和GUI E2E一样的测试框架也是从黑盒的角度来对被测控件做功能验证。
其次,每一个前端模块,都会构建自己的页面对象库,并且在此基础上封装开发自己的业务流程脚本。这些业务流程的脚本,可以组装成每个前端模块的测试用例。
以用户管理模块为例,测试用例的组装过程如下:
- 首先把用户管理模块中涉及到的所有页面比如登录页面、用户注册页面等按照页面对象模型的要求写成Page类
- 然后利用这些Page类封装业务流程脚本比如用户登录流程用户注册流程等
- 最后在GUI测试用例脚本中调用封装好的业务流程脚本构成该模块的GUI测试用例。
在这个阶段,测试用例需要完整覆盖该模块的所有业务逻辑以及相关的功能测试点,但是并不会实现所有测试用例的自动化。
**自动化测试用例的原则通常是优先选取业务关键路径以及Happy Path作为自动化测试的范围。在资源充裕的情况下我们希望这个阶段的自动化率可以达到70%-80%。** 所以,前端模块的质量保证主要依赖这部分测试。
如果你比较细心一定还记得我在之前的文章中有提到过“GUI的自动化测试往往只覆盖最核心且直接影响主营业务流程的E2E场景“并且”GUI测试遵循“手工测试为主自动化为辅”的策略而这里又建议说理想的自动化率应该达到70%~80%,是不是有点前后矛盾的感觉。
其实这是两个层面的测试这里70%-80%的GUI自动化覆盖率是针对模块级别的要求而“自动化测试为辅手工为主以及只覆盖核心业务场景”针对的是系统级别的E2E测试。这里容易引起混淆的点是模块测试和系统级别E2E测试都是属于GUI自动化测试的范畴。
最后组合各个前端模块并站在终端用户的视角以黑盒的方式使用网站的端到端E2E测试。 这部分的测试主要分为两大部分:
- 一部分是,通过探索式测试的方法手工执行测试,目标是尽可能多地发现新问题;
- 另一部分是通过GUI自动化测试执行基本业务功能的回归测试保证网站核心业务相关的所有功能的正确性。
虽然这部分端到端GUI测试用例的绝对数量不多往往是几百个的规模但是对于保证最终网站的质量却起着非常关键的作用。
可以这样说如果这些端到端的GUI自动化测试用例100%通过,那么上线后基本业务功能的质量就不会有大问题。所以,这部分测试工作的重要性不言而喻。
那么,**接下来的问题是应该由谁来开发这部分端到端的GUI自动化测试用例呢**
每个前端模块都会有对应的Scrum团队他们会负责开发该模块的页面对象模型、业务流程脚本以及测试用例。而端到端的GUI自动化测试不隶属于任何一个Scrum团队。
这种情况下最好的做法就是成立一个专门的测试团队负责这种系统级别的GUI测试。这样的团队往往被称为E2E测试团队。
很显然如果由E2E团队从无到有地开发这部分GUI自动化测试的脚本效率低下。而且这部分测试会涉及很多前端模块当各个前端模块的需求、业务流程以及页面实现有任何变动时E2E团队都很难做到及时更新。
所以,**解决这个问题的最佳实践就是E2E团队应该尽可能地利用各个模块已有的页面对象和业务流程脚本组装端到端的GUI测试。**
这样一方面最大程度地减少了重复工作另一方面可以把各个模块的变更及时反映到端到端的GUI测试中因为端到端的GUI测试用例是直接调用各个模块的页面对象和业务流程脚本而这些页面对象和业务流程脚本都是由每个模块自己的Scrum团队维护的。
而为了能够在端到端的GUI自动化测试中复用各个模块的页面对象和业务流程脚本我们就必须考虑的问题也就是我今天要和你探讨的第二个话题GUI自动化测试脚本应该如何组织
## 大型全球化电商网站的GUI自动化测试脚本管理
原有的方案不能解决端到端的GUI自动化测试复用各个模块的页面对象和业务流程脚本的问题在不断的实践中我总结了一个如图1所示的脚本组织结构来解决这个问题。
<img src="https://static001.geekbang.org/resource/image/bf/49/bf72fc0dc07739f21c3ea3de30b01049.png" alt="" />
也就是说,将各个模块的页面对象和业务流程脚本放在各自的代码库中,并引入页面对象和业务流程脚本的版本管理机制,通常采用页面对象和业务流程脚本的版本号和开发版本号保持一致的方案。
比如模块A的版本号是V1.0.0那么对应的页面对象库和业务流程脚本的版本号也应该是V1.0.0。
在端到端的GUI自动化测试脚本中引用各个模块正确的页面对象和业务流程脚本的版本号测试用例代码就可以直接调用模块的页面对象和业务流程脚本了。
具体在测试项目中模块版本的依赖往往是用POM来配置的如图2展示了一个典型测试项目的POM文件中的版本依赖关系其中引用了两个模块appcommon模块对应的就是上文提到的“公共组件库”而app.buy对应的就是具体依赖的前端模块。
由于这只是一个示例所以我只保留了两个依赖模块实际的端到端GUI测试项目往往会包含大量的模块依赖。
<img src="https://static001.geekbang.org/resource/image/c6/e2/c6caeaafc81a517ddbe976e6d3db6de2.png" alt="" />
在这种管理机制下E2E团队不需要重复开发任何的页面对象和业务流程脚本而且可以始终保证与各个模块的最新实现同步同时端到端的GUI测试用例脚本也会比较稳定不会因为各个模块的改动而频繁地修改。
这样一来E2E团队就会有更多的时间和精力去设计并执行探索式测试发现更多的潜在缺陷形成良性循环。
## 总结
我从实战的角度介绍了大型全球化电商网站GUI测试的策略设计以及测试脚本管理的问题
首先要从前端组件的级别来保证质量也就是需要对那些自定义开发的组件进行完整全面的测试。通常前端组件会基于Jest做比较严格的单元测试。
其次,每一个前端模块,都会构建自己的页面对象库,并且在此基础上封装开发自己的业务流程脚本。这些业务流程的脚本,可以组装成每个前端模块的测试用例。
最后,把各个前端模块组合在一起之后,站在终端用户的视角以黑盒的方式使用网站的端到端的测试。端到端的测试应该尽可能多地重用各个模块的页面对象库和业务流程脚本来完成。
而为了能够在端到端的GUI自动化测试中复用各个模块的页面对象和业务流程脚本我建议的方案是对各个前端业务模块的页面对象库和业务流程脚本实施版本化管理机制。
## 思考题
你所在的公司或者项目团队是否已经或者正计划开展E2E GUI测试开展过程中遇到过什么难题你们又是如何解决的
欢迎你给我留言。

View File

@@ -0,0 +1,210 @@
<audio id="audio" title="20 | 与时俱进:浅谈移动应用测试方法与思路" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9e/d5/9ea000ef7c21318669ae0da57b467ed5.mp3"></audio>
你好,我是茹炳晟。我今天分享的主题是“与时俱进:浅谈移动应用测试方法与思路”。
在GUI自动化测试这个系列我讲了很多基于浏览器的业务测试的内容你可能会说现在移动App大行其道对移动应用测试的方法和思路才更重要。
确实现今移动互联网蓬勃发展很多互联网应用的流量大部分已经不是来自于传统PC端的Web浏览器而是来自于移动端。
图1展示了最近12个月来亚洲地区的流量分布统计可见现如今将近三分之二的流量是来自于手机端的剩下的三分之一来自于传统PC端还有很少一部分流量来自于平板电脑其实这部分也可以归为移动端
<img src="https://static001.geekbang.org/resource/image/59/31/59e6df9d206104e69d94736997509a31.png" alt="" />
但是在我看来无论是移动端测试还是PC端测试都属于GUI测试的范畴所以基本的测试思路比如基于页面对象封装和基于业务流程封装的思想是相通的之前介绍的那些脚本分层的实现方法也都同样适用于移动端的GUI测试。
与此同时移动端应用的测试也会因为其自身特点有一些独特的测试方法与思路。严格来讲移动端应用又可以进一步细分为三大类Web App、Native App和Hybrid App。所以我今天分享的内容重点就是这三类移动应用的测试方法以及移动专项测试的思路与方法。
## 三类移动应用的特点
**Web App指的是移动端的Web浏览器** 其实和PC端的Web浏览器没有任何区别只不过Web浏览器所依附的操作系统不再是Windows和Linux了而是iOS和Android了。
Web App采用的技术主要是传统的HTML、JavaScript、CSS等Web技术栈当然现在HTML5也得到了广泛的应用。另外Web App所访问的页面内容都是放在服务器端的本质上就是Web网页所以天生就是跨平台的。
**Native App指的是移动端的原生应用** 对于Android是apk对于iOS就是ipa。Native App是一种基于手机操作系统iOS和Android并使用原生程序编写运行的第三方应用程序。
Native App的开发Android使用的语言通常是JavaiOS使用的语言是Objective-C。通常来说Native App可以提供比较好的用户体验以及性能而且可以方便地操作手机本地资源。
**Hybrid App俗称混血应用是介于Web App和Native App两者之间的一种App形式。**
Hybrid App利用了Web App和Native App的优点通过一个原生实现的Native Container展示HTML5的页面。更通俗的讲法可以归结为在原生移动应用中嵌入了Webview然后通过该Webview来访问网页。
Hybrid App具有维护更新简单用户体验优异以及较好的跨平台特性是目前主流的移动应用开发模式。
<img src="https://static001.geekbang.org/resource/image/1c/67/1c3526428800a068d56dc8e194645867.png" alt="" />
## 三类不同移动应用的测试方法
了解了Web App、Native App和Hybrid App这三类应用的特性接下来我就跟你说说它们的测试方法。
好了,我们已经知道了移动应用的三个主要种类,接下来我们从测试的角度再来看看这三类不同的移动应用。
对于Web App显然其本质就是Web浏览器的测试我在前面文章中介绍的所有GUI自动化测试的方法和技术比如数据驱动、页面对象模型、业务流程封装等都适用于Web App的测试。
如果你的Web页面是基于自适应网页设计即符合Responsive Web设计的规范而且你的测试框架如果支持Responsive Page那么原则上你之前开发的运行在PC Web端的GUI自动化测试用例不做任何修改就可以直接在移动端的浏览器上直接执行当然运行的前提是你的移动端浏览器必须支持Web Driver。
其中自适应网页设计Responsive Web Design是指同一个网页能够自动识别屏幕分辨率、并做出相应调整的网页设计技术。比如图3所示的例子就是同一个网页在不同分辨率下的不同展示效果。
<img src="https://static001.geekbang.org/resource/image/cf/b3/cf8b774a44899110d2ba2dfbaa9a4db3.png" alt="" />
对Native App的测试虽然不同的平台会使用不同的自动化测试方案比如iOS一般采用XCUITest Driver而Android一般采用UiAutomator2或者Espresso等但是数据驱动、页面对象以及业务流程封装的思想依旧适用你完全可以把这些方法应用到测试用例设计中。
对Hybrid App的测试情况会稍微复杂一点对Native Container的测试可能需要用到XCUITest或者UiAutomator2这样的原生测试框架而对Container中HTML5的测试基本和传统的网页测试没什么区别所以原本基于GUI的测试思想和方法都能继续适用。
唯一需要注意的是Native Container和Webview分别属于两个不同的上下文ContextNative Container默认的Context为“NATIVE APP&quot;而Webview默认的Context为“WEBVIEW_+被测进程名称”。
所以当需要操作Webview中的网页元素时需要先切换到Webview的Context下如图4所示代码就完成了这一切换操作。
<img src="https://static001.geekbang.org/resource/image/35/18/35549fade212214212730ab91de83518.png" alt="" />
如此看来移动端的测试除了使用的测试框架不同以外测试设计本身和GUI测试有异曲同工之妙似乎并没有什么新的内容那真的是这样吗
答案显然是否定的。
## 移动应用专项测试的思路和方法
对于移动应用,顺利完成全部业务功能测试往往是不够的。如果你的关注点只是业务功能测试,那么,当你的移动应用被大量用户安装和使用时,就会暴露出很多之前完全没有预料到的问题,比如:
- 流量使用过多;
- 耗电量过大;
- 在某些设备终端上出现崩溃或者闪退的现象;
- 多个移动应用相互切换后,行为异常;
- 在某些设备终端上无法顺利安装或卸载;
- 弱网络环境下,无法正常使用;
- Android环境下经常出现ANR(Application Not Responding)
-
这样的问题还有很多,为了避免或减少此类情况的发生,所以移动应用除了进行常规的功能测试外,通常还会进行很多移动应用所特有的专项测试。
今天这篇文章我就从交叉事件测试、兼容性测试、流量测试、耗电量测试、弱网络测试、边界测试这6个最主要的专项测试来展开。
**第一,交叉事件测试**
交叉事件测试也叫中断测试是指App执行过程中有其他事件或者应用中断当前应用执行的测试。
比如App在前台运行过程中突然有电话打进来或者收到短信再或者是系统闹钟等等情况。所以在App测试时就需要把这些常见的中断情况考虑在内并进行相关的测试。
注意,**此类测试目前基本还都是采用手工测试的方式,并且都是在真机上进行,不会使用模拟器。**
首先,采用手工测试的原因是,此类测试往往场景多,而且很多事件很难通过自动化的方式来模拟,比如呼入电话、接收短信等,这些因素都会造成自动化测试的成本过高,得不偿失,所以工程实践中,交叉事件测试往往全是基于手工的测试。
其次,之所以采用真机,是因为很多问题只会在真机上才能重现,采用模拟器测试没有意义。
交叉事件测试,需要覆盖的场景主要包括:
- 多个App同时在后台运行并交替切换至前台是否影响正常功能
- 要求相同系统资源的多个App前后台交替切换是否影响正常功能比如两个App都需要播放音乐那么两者在交替切换的过程中播放音乐功能是否正常
- App运行时接听电话
- App运行时接收信息
- App运行时提示系统升级
- App运行时发生系统闹钟事件
- App运行时进入低电量模式
- App运行时第三方安全软件弹出告警
- App运行时发生网络切换比如由Wifi切换到移动4G网络或者从4G网络切换到3G网络等
-
其实你可以发现,这些需要覆盖的场景,也是我们今后测试的测试用例集,每一场景都是一个测试用例的集合。
**第二,兼容性测试**
兼容性测试顾名思义就是要确保App在各种终端设备、各种操作系统版本、各种屏幕分辨率、各种网络环境下功能的正确性。常见的App兼容性测试往往需要覆盖以下的测试场景
- 不同操作系统的兼容性包括主流的Andoird和iOS版本
- 主流的设备分辨率下的兼容性;
- 主流移动终端机型的兼容性;
- 同一操作系统中,不同语言设置时的兼容性;
- 不同网络连接下的兼容性比如Wifi、GPRS、EDGE、CDMA200等
- 在单一设备上与主流热门App的兼容性比如微信、抖音、淘宝等
-
**兼容性测试,通常都需要在各种真机上执行相同或者类似的测试用例,所以往往采用自动化测试的手段。** 同时由于需要覆盖大量的真实设备除了大公司会基于Appium + Selenium Grid + OpenSTF去搭建自己的移动设备私有云平台外其他公司一般都会使用第三方的移动设备云测平台完成兼容性测试。
第三方的移动设备云测平台国外最知名的是SauceLab国内主流的是Testin。
**第三,流量测试**
由于App经常需要在移动互联网环境下运行而移动互联网通常按照实际使用流量计费所以如果你的App耗费的流量过多那么一定不会很受欢迎。
流量测试,通常包含以下几个方面的内容:
- App执行业务操作引起的流量
- App在后台运行时的消耗流量
- App安装完成后首次启动耗费的流量
- App安装包本身的大小
- App内购买或者升级需要的流量。
**流量测试往往借助于Android和iOS自带的工具进行流量统计也可以利用tcpdump、Wireshark和Fiddler等网络分析工具。**
对于Android系统网络流量信息通常存储在/proc/net/dev目录下也可以直接利用ADB工具获取实时的流量信息。另外我还推荐一款Android的轻量级性能监控小工具Emmagee类似于Windows系统性能监视器能够实时显示App运行过程中CPU、内存和流量等信息。
对于iOS系统可以使用Xcode自带的性能分析工具集中的Network Activity分析具体的流量使用情况。
但是流量测试的最终目的并不是得到App的流量数据而是要想办法减少App产生的流量。虽然减少App消耗的流量不是测试工程师的工作但了解一些常用的方法也将有助于你的测试日常工作
- 启用数据压缩,尤其是图片;
- 使用优化的数据格式比如同样信息量的JSON文件就要比XML文件小
- 遇到既需要加密又需要压缩的场景,一定是先压缩再加密;
- 减少单次GUI操作触发的后台调用数量
- 每次回传数据尽可能只包括必要的数据;
- 启用客户端的缓存机制;
-
**第四,耗电量测试**
耗电量也是一个移动应用能否成功的关键因素之一。
在目前的生态环境下能提供类似服务或者功能的App往往有很多如果在功能类似的情况下你的App特别耗电、让设备发热比较严重那么你的用户一定会卸载你的App而改用其他App。最典型的就是地图等导航类的应用对耗电量特别敏感。
耗电量测试通常从三个方面来考量:
- App运行但没有执行业务操作时的耗电量
- App运行且密集执行业务操作时的耗电量
- App后台运行的耗电量。
耗电量检测既有基于硬件的方法也有基于软件的方法。我所经历过的项目都是采用软件的方法Android和iOS都有各自自己的方法
- Android通过adb命令“adb shell dumpsys battery”来获取应用的耗电量信息
- iOS通过Apple的官方工具Sysdiagnose来收集耗电量信息然后可以进一步通过Instrument工具链中的Energy Diagnostics进行耗电量分析。
**第五,弱网络测试**
与传统桌面应用不同,移动应用的网络环境比较多样,而且经常出现需要在不同网络之间切换的场景,即使是在同一网络环境下,也会出现网络连接状态时好时坏的情况,比如时高时低的延迟、经常丢包、频繁断线,在乘坐地铁、穿越隧道,和地下车库的场景下经常会发生。
所以,**移动应用的测试需要保证在复杂网络环境下的质量。具体的做法就是在测试阶段模拟这些网络环境在App发布前尽可能多地发现并修复问题。**
在这里我推荐一款非常棒的开源移动网络测试工具Facebook的Augmented Traffic ControlATC
ATC最好用的地方在于它能够在移动终端设备上通过Web界面随时切换不同的网络环境同时多个移动终端设备可以连接到同一个Wifi各自模拟不同的网络环境相互之间不会有任何影响。也就是说只要搭建一套ATC就能满足你所有的网络模拟需求。
如果你对ATC感兴趣可以在[它的官方网站](http://facebook.github.io/augmented-traffic-control/)找到详细的使用说明。
**第六,边界测试**
**边界测试是指移动App在一些临界状态下的行为功能的验证测试基本思路是需要找出各种潜在的临界场景并对每一类临界场景做验证和测试。** 主要的场景有:
- 系统内存占用大于90%的场景;
- 系统存储占用大于95%的场景;
- 飞行模式来回切换的场景;
- App不具有某些系统访问权限的场景比如App由于隐私设置不能访问相册或者通讯录等
- 长时间使用App系统资源是否有异常比如内存泄漏、过多的链接数等
- 出现ANR的场景
- 操作系统时间早于或者晚于标准时间的场景;
- 时区切换的场景;
-
## 总结
好了,最后我来总结一下今天的主要的知识点:
移动应用根据技术架构的不同主要分为Web App、Native App和Hybrid App三大类这三类应用的测试方法本质上都属于GUI测试的范畴。
从业务功能测试的角度看移动应用的测试用例设计和传统PC端的GUI自动化测试策略比较类似只是测试框架不同数据驱动、页面对象模型和业务流程封装依旧适用
各种专项测试是移动应用的测试重点也有别于传统GUI测试。专项测试包括交叉事件测试、兼容性测试、流量测试、耗电量测试、弱网络测试和边界测试。
## 思考题
请你谈谈对移动应用测试的看法,你所在的企业,是如何开展移动测试的?你们又涉及了哪些类型的专项测试?
欢迎你给我留言。

View File

@@ -0,0 +1,216 @@
<audio id="audio" title="21 | 移动测试神器带你玩转Appium" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d0/aa/d0dca0eb56a2fb24db15ffa6eeb66faa.mp3"></audio>
在上一篇文章中我介绍了Web App、Native App和Hybrid App三种不同类型的移动应用以及对应的测试设计方法也介绍了移动应用所特有的专项测试知识。
今天我就以移动应用的自动化测试为主题介绍目前主流的移动应用自动化测试框架Appium。Appium 是一个开源的自动化测试框架支持iOS和Android上Web App、Native App和Hybrid App的自动化测试。
由于基于Appium的移动应用环境搭建相对复杂虽然网上也有不少教程但是知识点都比较零碎而且大多都是基于早期版本的示例所以我会使用最新版本的Appium Desktop 1.6.2和Appium Server 1.8.1来展开今天的内容:
- 首先我会展示如何在Mac环境下一步一步地搭建Appium测试环境
- 接下来我以iOS为例实际开发两个测试用例一个是Native App的测试用例另一个是Web App的测试用例因为Hybird App的测试用例其实是类似的Native App的壳Web App的内容所以就不再单独举例子了
- 然后我会在iOS的模拟器上实际执行这两个测试用例之所以选择iOS模拟器而不用iOS真机做例子是因为iOS真机的测试需要用到Apple开发者账号还需要对被测应用进行签名等会在环境搭建过程中引入很多额外步骤而这些步骤对于讲解Appium并没有直接的关系
- 最后当你已经通过实际使用对Appium形成感性认识后我再来简单介绍一下Appium的内部原理让你做到知其然知其所以然。
## 移动应用的自动化测试需求
在开始设计测试用例前,我们首先需要明确要开发的这两个自动化测试用例的具体测试需求。
<li>
<p>Native App的测试用例被测App我选用了Appium官方的示例App被测App的源代码可以通过“[https://github.com/appium/ios-test-app”](https://github.com/appium/ios-test-app%E2%80%9D) 下载然后在Xcode中编译打包成TestApp.app。<br />
具体的测试需求是输入两个数字然后点击“Compute Sum”验证两个数字相加后的结果是否正确。</p>
</li>
<li>
Web App的测试用例具体需求是在iPhone上打开Safari浏览器访问Appium的官方主页“[http://appium.io](http://appium.io)”然后验证主页的标题是否是“Appium: Mobile App Automation Made Awesome”。
</li>
<img src="https://static001.geekbang.org/resource/image/39/26/391e5c3efac446b7cfc76fc1620cb626.png" alt="" />
接下来我将从最初的环境搭建开始和你来一起开发iOS上的Native App和Web App的测试用例。首先我们看一下iOS的环境搭建如果你之前没有接触过这部分内容你可以跟着我的步骤一步一步来做而如果你已经比较熟悉Xcode的话可以跳过这部分内容直接从“Appium环境搭建”部分看起。
## iOS环境搭建
在正式搭建Appium环境前我们先来搭建iOS开发环境
- 首先下载安装Xcode
- 然后在Xcode中下载iOS的模拟器
- 接着使用Xcode编译打包被测试App
- 最后在iOS的模拟器中尝试手工执行这两个测试用例。
在iOS模拟器中手动执行测试用例的具体操作步骤如下
<li>
启动Xcode导入ios-test-app下的TestApp.xcodeproj项目。
</li>
<li>
在Xcode中打开“Preferences”中的“Components”完成iOS 10.0 Simulator的下载。
</li>
<li>
在Xcode的“General”页面将TestApp的“Deployment Target”设置为10.0并且将“Devices”设置为“iPhone”如图2所示。
</li>
<img src="https://static001.geekbang.org/resource/image/fa/83/faad103928afd3011e0c273ce3e22683.png" alt="" />
<li>
在Xcode中编译运行TestApp之后系统会自动启动iPhone模拟器自动完成TestApp的安装并在iPhone模拟器中自动启动TestApp。
</li>
<li>
在TestApp中手动执行自定义的加法测试用例。
</li>
<li>
退出TestApp然后打开Safari浏览器在Safari中执行访问Appium官方主页的测试用例。
</li>
至此你已经搭建好了iOS开发环境并且成功编译打包了TestApp。接下来我们再一起来搭建Appium测试环境并尝试在Appium中开发上述的两个测试用例。
## Appium测试环境搭建
通过Appium的官方网站下载并安装最新版本的Appium截止本文写作的时间最新版本是Appium-1.6.2.dmg。
需要注意的是早期版本和网上很多教程都建议用命令行的形式启动Appium Server但在这里我是想强调的是你完全可以通过界面启动在Launchpad中找到Appium的图标点击即可启动而且新版本的Appium也推荐这个启动方式。通过界面启动是目前最简单直接的方式。
然后你需要用命令行“npm install -g appium-doctor”安装Appium的环境诊断工具appium-doctor用于检查Appium所依赖的相关环境变量以及其他安装包是否都已经配置好了。如果还没有就需要逐个安装并根据appium-doctor的提示配置环境变量。
这里Appium最主要的依赖项主要有Java、Node.js、Xcode、Carthage、Android SDK、adb等。如果你所有的环境依赖都正常配置的话你就会看到appium-doctor返回这样一个截图如图3所示。
<img src="https://static001.geekbang.org/resource/image/3b/a8/3b8915195cade244fe094a37a1a295a8.png" alt="" />
按照上面的步骤配置好Appium的环境依赖后就可以继续启动Appium Server了。
## Appium Inspector的使用
为了后续测试用例的顺利执行我们可以先来熟悉一下Appium Inspector的使用。Appium Inspector主要是用来协助对界面元素进行定位的工具。
首先我们来看看如何使用Appium Inspector启动iPhone的模拟器并在模拟器上运行TestApp以及如何通过Inspector定位TestApp界面上的元素了解元素的定位是后续开发自动化脚本的基础。具体的操作过程如下。
1. 通过Appium Server的“Start Inspector Session”按钮进入Session配置界面。
<img src="https://static001.geekbang.org/resource/image/5a/b9/5a4a49e29f98057e75a7f18dedaac5b9.png" alt="" />
<li>在Session配置界面完成必要参数的配置。这里你需要根据选用的移动设备操作系统、模拟器/真机等具体情况来完成参数配置工作。需要配置的参数主要包括platformName、platformVersion、DeviceName、automationName和app。<br />
其中automationName指自动化测试框架的名称这里采用了XCUITestapp指被测Native App的安装包路径这里使用之前Xcode打包生成的TestApp.app这样启动模拟器时就会自动把TestApp.app安装到模拟器中。<br />
其他参数的配置非常简单,我就不再一一展开了。</li>
<img src="https://static001.geekbang.org/resource/image/b4/79/b4fbfef46aa425e1c2b1c51c6811b179.png" alt="" />
1. 完成配置后点击Session界面的“Start Session”按钮启动iPhone模拟器并在iPhone模拟器中启动TestApp同时还会打开Inspector窗口。如图6所示。
<img src="https://static001.geekbang.org/resource/image/cd/d5/cd95332b27674b7986c183b967c8a9d5.png" alt="" />
1. 在Inspector窗口我们可以利用“Select Elements”功能通过点击元素显示Native App上的元素定位信息。如图7所示。
<img src="https://static001.geekbang.org/resource/image/31/9a/31ed3e4326ae3f3494b78b49c9f6bd9a.png" alt="" />
1. 在Inspector窗口可以通过“Recording”功能生成不同语言的自动化脚本。比如在启用了“Recording”功能后点击“Compute Sum”按钮就会生成如图8所示的自动化脚本片段。
<img src="https://static001.geekbang.org/resource/image/b2/5d/b28a1e0f1d8c9d571c675b2062ba455d.png" alt="" />
了解了如何通过Inspector获取元素定位信息的方法之后我们就来正式开发基于Appium的第一个Web App和第一个Native App的测试用例。
## 基于Appium开发你的第一个Native App的测试用例
**第一步建立一个空的Maven项目然后在POM文件中加入如图9所示的依赖。**
在这个案例里面我们会使用TestNG组织测试用例所以代码的第14行加入了TestNG的依赖。
第19行的java-client是关键java-client的作用是利用Java代码将测试用例中的操作步骤发送给Appium Server然后由Appium Server自动完成这些操作。
目前Appium支持多种编程语言每种语言都有自己的client比如这里使用Java语言所以引入了java-client如果你使用Python语言那么就需要引用python-client。
<img src="https://static001.geekbang.org/resource/image/09/59/0900c83c8262f90c0635626fa990f459.png" alt="" />
**第二步创建一个类并命名为“iOS_NativeApp_DemoTest”然后按照如图10所示的代码实现这个class。**
注意这里的代码是真实的可执行Java代码你可以直接拿去使用。
<img src="https://static001.geekbang.org/resource/image/32/2e/32022f6b0166fea7498cbbdc0af9a22e.png" alt="" />
<li>代码第21行的@BeforeTest第38行的@AfterTest以及第44行的@Test都是利用了TestNG的annotation对函数进行标注。<br />
标有@Test的函数是真正的测试主体,所有测试相关的步骤都放在这个函数中;<br />
标有@ BeforeTest的函数会在@Test函数之前执行测试的相关准备工作图中的代码用这个函数完成了DesiredCapabilities的设置并用该Capabilities构造了iosdriver<br />
标有@ AfterTest的函数在@Test函数执行结束后执行主要用于环境的清理和收尾图示的代码用这个函数完成了iosdriver的退出操作。</li>
- 代码的第24-33行构造了DesiredCapabilities对象并对APPIUM_VERSION、PLATFORM_VERSION、PLATFORM_NAME、AUTOMATION_NAME、DEVICE_NAME和APP等参数进行了设置。其中APP的值是被测Native App安装包的绝对路径。
<li>代码的第46-58行是测试用例的主体部分主要分为三部分<br />
第47-50行通过iosdriver的findElementByAccessibilityId方法定义了页面上的四个元素分别是输入参数框A、输入参数框B、计算按钮和加法结果显示框。代码中具体的AccessibilityId可以通过Inspector获取。<br />
第53-55行通过自定义元素的操作执行加法运算。<br />
第58行通过断言方法assertEquals验证加法运算的结果。</li>
**第三步为了运行这个TestNG的测试用例我们需要再添加一个testng.xml文件** 具体内容如图11所示。
<img src="https://static001.geekbang.org/resource/image/ea/0f/eadddd658205bdce9ddfa2488fe6130f.png" alt="" />
**第四步在保证Appium Server已经启动的情况下就可以运行testng.xml执行测试了。** 测试开始后首先会自动启动基于iOS 10.0的iPhone 7模拟器然后依次自动完成WebDriverAgentWDA和被测Native App的安装。
WDA是由Facebook开源的支持iOS自动化的代理工具其底层通过XCUItest实现自动化。
接着就会自动运行被测Native App并根据@Test函数中定义的步骤完成自动化测试的步骤和验证
到此我们的第一个基于Appium的Native App自动化测试用例就设计完了。
## 基于Appium开发你的第一个Web App的测试用例
有了Native App测试用例的设计基础再来实现一个基于Appium的Web App自动化测试用例就简单得多了。
**第一步在上述的Maven项目中再创建一个类并命名为“iOS_WebApp_DemoTest”然后按照如图12所示的代码实现这个类。**
<img src="https://static001.geekbang.org/resource/image/91/16/911057393796c62e5d854607299ba216.png" alt="" />
代码的整体结构和上述Native App测试用例的完全一致只有一个地方需要特别注意代码的第29行由于Web App是基于浏览器的测试所以这里不需要指定App这个参数而是直接用BROWSER_NAME指定浏览器的名字即可。
对于测试用例的主体部分也就是代码的第45-47行就比较简单了首先打开Safari浏览器并访问“[http://appium.io/](http://appium.io/)”接着用断言方法assertEquals验证页面的Title是不是“Appium: Mobile App Automation Made Awesome.”。其中实际页面的Title可以通过mobiledriver的getTitle方法获得。
**第二步在testng.xml中添加这个Web App的测试用例然后我们就可以在Appium Server已经启动的情况下执行这个测试用例了。**
这个测试用例首先会自动启动基于iOS 10.0的iPhone 7模拟器然后自动打开Safari浏览器并访问Appium的官方网站。执行完成后的界面如下图13所示。
<img src="https://static001.geekbang.org/resource/image/7a/9c/7ada743125c6412e20b0b944b479559c.png" alt="" />
进行到这里我们基于Appium开发的第一个Web App的自动化测试用例也就开发完成了。
经过前面Appium环境搭建以及两个测试用例的设计相信你已经对Appium有了一个感性的认识了。那么Appium的实现原理又是怎样的呢理解了Appium的使用原理可以帮助你更好地使用这个工具设计更加“有的放矢”的测试用例。
## Appium的实现原理
Appium作为目前主流的移动应用自动化测试框架具有极强的灵活性主要体现在以下5个方面
- 测试用例的实现支持多种编程语言比如Java、Ruby、Python等
- Appium Server支持多平台既有基于Mac的版本也有基于Windows的版本
- 支持Web App、Native App和Hybird App三大类移动应用的测试
- 既支持iOS也支持Android
- 既支持真机,也支持模拟器。
实际应用中你可以根据项目情况灵活组合完成移动应用的自动化测试。比如用Java写iOS上的Native App的测试用例测试用例跑在Mac平台的iPhone虚拟机上或者用Python写Android上的Web App的测试用例测试用例通过Windows平台跑在Android的真机上。
这样的组合还有很多很多。那你有没有想过Appium为什么可以做到如此强大的灵活性呢这就要从Appium的基本原理讲起了。
要真正理解Appium的内部原理你可以把Appium分成三大部分分别是Appium Client、Appium Server和设备端。这三部分的关系如图14所示。
<img src="https://static001.geekbang.org/resource/image/97/ef/97a2e84f7766d8ee38eb3923b4b9d8ef.png" alt="" />
**我们先来看看处于中间位置的Appium Server。**
Appium Server有Mac和Windows版本也就是说Appium Server可以运行在Mac或者Windows电脑上。本质上Appium Server是一个 Node.js 应用接受来自Appium Client的请求解析后通过WebDriver协议和设备端上的代理打交道。
- 如果是iOSAppium Server会把操作请求发送给WebDriverAgent简称WDA然后WDA再基于XCUITest完成iOS模拟器或者真机上的自动化操作
- 如果是AndroidAppium Server会把操作请求发送给appium-UIautomator2-server然后appium-UIautomator2-server再基于UIAutomator V2完成Android模拟器或者真机上的自动化操作。
Appium Client其实就是测试代码使用对应语言的Client将基于JSON Wire协议的操作指令发给Appium Server。
整体来说Appium的内部原理可以总结为**Appium属于C/S架构Appium Client通过多语言支持的第三方库向Appium Server发起请求基于Node.js的Appium Server会接受Appium Client发来的请求接着和iOS或者Android平台上的代理工具打交道代理工具在运行过程中不断接收请求并根据 WebDriver 协议解析出要执行的操作最后调用iOS或者Android平台上的原生测试框架完成测试。**
## 总结
好了,我来总结一下今天的主要的内容:
目前网络上Appium工具使用相关的资料都比较零散为此我以最新版本的Appium Desktop 1.6.2和Appium Server 1.8.1为例手把手地带你搭建了iOS环境以及Appium测试环境并介绍了如何通过Appium Inspector来定位页面元素。
搭建好了测试环境后我分别针对Native App和Web App这两类移动应用基于Appium实现了两个测试用例这也是我在这个专栏里面为你实现的第一个移动应用的测试用例。虽然测试需求比较简单但是你也可以从中体会到移动应用测试用例设计的思想、方法。
最后本着知其然知其所以然的原则我介绍了Appium的实现原理它属于C/S架构Appium Client通过第三方库向Appium Server发起请求Appium Server接受请求然后和移动平台上的代理工具打交道代理工具在运行过程中不断接收来自Appium Server的请求并解析出要执行的操作最后调用移动平台原生的测试框架完成测试操作。
## 思考题
我在这篇文章里面举的例子都是基于iOS的建议你基于Android分别实现一个Web App和Native App的测试用例。
如果实现过程中,遇到了问题,或者有一些自己的想法,请给我留言讨论吧。