mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-16 06:03:45 +08:00
mod
This commit is contained in:
163
极客时间专栏/软件测试52讲/GUI自动化测试篇/12 | 从0到1:你的第一个GUI自动化测试.md
Normal file
163
极客时间专栏/软件测试52讲/GUI自动化测试篇/12 | 从0到1:你的第一个GUI自动化测试.md
Normal 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 Server,Selenium RC方案的另一大部分就是,Client Libraries。它们的具体关系如图3所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/30/b6/30bb6d776cd499b727d83fa4499ca9b6.png" alt="">
|
||||
|
||||
**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的基本原理,我从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 Core(JavaScript函数的集合)加载到浏览器页面当中,并同时把浏览器的代理设置为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自动化测试框架?你知道它们的内部实现原理吗?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
122
极客时间专栏/软件测试52讲/GUI自动化测试篇/13 | 效率为王:脚本与数据的解耦 + Page Object模型.md
Normal file
122
极客时间专栏/软件测试52讲/GUI自动化测试篇/13 | 效率为王:脚本与数据的解耦 + Page Object模型.md
Normal 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开发,还是用商用的QTP(Quick 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自动化测试项目中,这也是我希望达到的“授人以鱼,不如授人以渔”。
|
||||
|
||||
## 思考题
|
||||
|
||||
我在文中有这样一段描述:页面对象模型的核心理念是,以页面为单位来封装页面上的控件以及控件的部分操作。但是,现在业界对“是否应该在页面对象模型中封装控件的操作”一直有不同的看法。
|
||||
|
||||
有些观点认为,可以在页面对象模型中封装页面控件的操作;而有些观点则认为,页面对象模型只封装控件,而操作应该再做一层额外的封装。
|
||||
|
||||
你更认同哪种观点呢,说说你的理由吧。
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
159
极客时间专栏/软件测试52讲/GUI自动化测试篇/14 | 更接近业务的抽象:让自动化测试脚本更好地描述业务.md
Normal file
159
极客时间专栏/软件测试52讲/GUI自动化测试篇/14 | 更接近业务的抽象:让自动化测试脚本更好地描述业务.md
Normal 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("username");
|
||||
4: loginFlowParameters.setPassword("password");
|
||||
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("bookname");
|
||||
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自动化测试是否已经运用了业务流程级别的封装?在使用过程中,你是否遇到什么瓶颈,是如何解决的?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
171
极客时间专栏/软件测试52讲/GUI自动化测试篇/15 | 过不了的坎:聊聊GUI自动化过程中的测试数据.md
Normal file
171
极客时间专栏/软件测试52讲/GUI自动化测试篇/15 | 过不了的坎:聊聊GUI自动化过程中的测试数据.md
Normal 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测试的测试数据的?遇到了哪些问题,对应的有哪些解决方案呢?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
@@ -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自动化测试的效率吗?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
151
极客时间专栏/软件测试52讲/GUI自动化测试篇/17 | 精益求精:聊聊提高GUI测试稳定性的关键技术.md
Normal file
151
极客时间专栏/软件测试52讲/GUI自动化测试篇/17 | 精益求精:聊聊提高GUI测试稳定性的关键技术.md
Normal 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测试不稳定的因素,你又是如何来解决的?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
137
极客时间专栏/软件测试52讲/GUI自动化测试篇/18 | 眼前一亮:带你玩转GUI自动化的测试报告.md
Normal file
137
极客时间专栏/软件测试52讲/GUI自动化测试篇/18 | 眼前一亮:带你玩转GUI自动化的测试报告.md
Normal 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测试报告的功能,你还能想到其他更多实用的功能吗?你又是如何实现这些功能的?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
130
极客时间专栏/软件测试52讲/GUI自动化测试篇/19 | 真实的战场:如何在大型项目中设计GUI自动化测试策略.md
Normal file
130
极客时间专栏/软件测试52讲/GUI自动化测试篇/19 | 真实的战场:如何在大型项目中设计GUI自动化测试策略.md
Normal 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测试?开展过程中,遇到过什么难题,你们又是如何解决的?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
210
极客时间专栏/软件测试52讲/GUI自动化测试篇/20 | 与时俱进:浅谈移动应用测试方法与思路.md
Normal file
210
极客时间专栏/软件测试52讲/GUI自动化测试篇/20 | 与时俱进:浅谈移动应用测试方法与思路.md
Normal 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使用的语言通常是Java,iOS使用的语言是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分别属于两个不同的上下文(Context),Native Container默认的Context为“NATIVE APP",而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 Control(ATC)。
|
||||
|
||||
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测试。专项测试包括:交叉事件测试、兼容性测试、流量测试、耗电量测试、弱网络测试和边界测试。
|
||||
|
||||
## 思考题
|
||||
|
||||
请你谈谈对移动应用测试的看法,你所在的企业,是如何开展移动测试的?你们又涉及了哪些类型的专项测试?
|
||||
|
||||
欢迎你给我留言。
|
||||
|
||||
|
||||
216
极客时间专栏/软件测试52讲/GUI自动化测试篇/21 | 移动测试神器:带你玩转Appium.md
Normal file
216
极客时间专栏/软件测试52讲/GUI自动化测试篇/21 | 移动测试神器:带你玩转Appium.md
Normal 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,指自动化测试框架的名称,这里采用了XCUITest;app指被测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模拟器,然后依次自动完成WebDriverAgent(WDA)和被测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协议和设备端上的代理打交道。
|
||||
|
||||
- 如果是iOS,Appium Server会把操作请求发送给WebDriverAgent(简称WDA),然后WDA再基于XCUITest完成iOS模拟器或者真机上的自动化操作;
|
||||
- 如果是Android,Appium 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的测试用例。
|
||||
|
||||
如果实现过程中,遇到了问题,或者有一些自己的想法,请给我留言讨论吧。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user