我们从这一节开始演示最适合用户验收测试的一种自动化测试——UI 自动化测试,当前情况下常见应用 UI 端主要包括 Web 应用(单体应用或多网页组成),移动应用(如 Android、iOS 等)。UI 端自动化测试有哪些好处呢?
- UI 端直接提供给用户使用,测试将直接反应用户是如何与系统交互的。
- 可以提供直观的测试结果截图供相关业务人员或老板参考。
- UI 端自动化测试可以减少人工测试工作量,提高验收测试效率,从而提高交付效率。
UI端自动化测试不是越多越好,也有其缺点。主要表现:
- UI 端自动化测试相对于接口测试和单元测试来说,运行速度很慢,且 UI 容易发生变化;
- 录制回放模式录制的 UI 自动化测试脚本可读性差,不容易维护,并且在 UI 端应用开发完成前不能够录制,因为录制回放模式得需要可运行的 UI 应用才能给录制,跟不上敏捷测试的要求;
- UI 端自动化脚本跨平台性差,例如 Web 应用自动化脚本对于 Firefox、Chrome、IE 等浏览器需要做不同的兼容处理。
UI 端自动化测试如果面面俱到,不分巨细,将导致运行效率极其低下,严重影响验收测试进度,也会导致敏捷研发团队中开发人员与测试人员之间的矛盾。
图1 Web 自动化测试真慢
提高 Web 自动化测试执行效率的一种方法是使用无头浏览器(Headless Browser),例如 HtmlUnit、PhantomJS。但是这些无头浏览器也有自身的一些缺陷,HtmlUnit 对 AJAX 和 JavaScript 支持有限,如果待测 Web 应用使用了大量 AJAX 和 JavaScript,可以选择使用 PhantomJS。尽管 PhantomJS 比 HtmlUnit 对 AJAX 和 JavaScript 的支持会好些,PhantomJS 也一定程度支持并行执行自动化测试,但是 PhantomJS 基于 WebKit 而并不能完全模拟火狐和 IE 等浏览器。如果 Web 应用中实现了大量的业务逻辑,要测试这些业务逻辑在不同的浏览器中确实可用,建议使用相关真实的浏览器去测试。
根据以上 UI 端 Web 应用测试的优点和缺点,没有必要把 Web 应用的方方面面都做自动化测试,而且全部 Web 应用都做自动化测试确实也不是什么好主意。从敏捷测试的角度来说,业务逻辑验证和功能性验证应该尽量在非 UI 层面进行,例如单元测试、集成测试(包括接口测试)。在单元测试和集成测试中自动化测试脚本运行效率高,并且能及时发现问题和定位问题。
图2 自动化测试分层
那么什么情况下进行 UI 端 Web 应用的自动化测试呢?我们可以考虑两点:
- 验证用户使用应用的轨迹路径,也就是用户的使用场景。
- 验证业务逻辑在 UI 端 Web 应用中是如何表现的。
我们举一个简单的例子说明一下,什么情况下使用 UI 端自动化测试和什么情况下使用单元测试或接口测试比较好。
假设我们有两个验证点:
- 如果用户输入的密码比较简单,不满足指定的最小长度6个字符,或不满足多种类型字符要求(密码不包含特殊#$%、字母等),应该给出提示信息“密码长度不能小于6个字符且应该包含特殊字符或字母!”。
- 密码必须大于等于6个字符并且包含至少一个特殊字符或字母。
第1个验证点由于需要在 UI 端 Web 页面检查相关提示信息“密码长度不能小于6个字符且应该包含特殊字符或字母!”,因此应该做 UI 端测试。
第2个验证点由于验证的是密码强度算法,可以在单元测试或接口测试中验证,没有必要在 UI 端测试。
UI 端 Web 应用自动化测试工具我们选择使用 Selenium WebDriver,这是一个开源且很流行的 Web 自动化测试工具。支持常见的 Chrome、Firefox、IE/Edge、Opera 和 Safari 等浏览器,同时也支持 PhantomJS 的无头浏览器模式。在操作系统方面支持 Microsoft Windows、Linux、Apple OSX 等,编程语言方面支持 Java 、C#、JavaScript、Python、Perl、PHP 等
图3 Selenium 支持多种浏览器
我们以 Java 语言为例说明 Selenium WebDriver 的用法。针对不同的浏览器,Selenium WebDriver 具有不同 Java 实现驱动类。
主要 WebDriver 驱动类见下表。
浏览器名称 | 驱动名称 | 下载地址 |
---|---|---|
Firefox | Mozilla GeckoDriver | https://github.com/mozilla/geckodriver/ |
Chrome | Google Chrome Driver | https://sites.google.com/a/chromium.org/chromedriver/ |
Opera | Opera | http://choice.opera.com/developer/tools/operadriver/ |
Edge | Microsoft Edge Driver | https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ |
Safari | SafariDriver | https://webkit.org/blog/6900/webdriver-support-in-safari-10/ |
我们以小明同学敏捷研发团队开发的机票销售系统为例,说明 Selenium WebDriver 的使用。由于各种浏览器的 Selenium WebDriver 实现的是相同的 WebDriver 接口,因此除了根据不同浏览器加载不同的 WebDriver 之外,各种浏览器中 WebDriver 的使用方式是一样的。下面以 Firefox 为例说明 WebDriver 的使用过程。
UI 端 Web 敏捷自动化测试的实现应该分为三层:业务规则层、业务流程层和技术层。业务规则层可读性最好,变化比较少;业务流程层可读性和易变化性次之;技术层的易变化性最高,技术专业性也更强。敏捷测试实现分层的好处在于可以促进团队分工协作和提高自动化脚本的可维护性。业务规则层可以有产品经理、需求分析和比较懂业务的测试人员完成,业务流程层可以结合自然语言转化技术生成自动化测试脚本框架,然后由自动化测试人员或开发人员完成,技术层可以由专业的自动化测试人员完成。团队分工提高自动化编写效率。另外业务规则的变更、业务流程的变更,UI 端的变更可以在相应的模块更改对应自动化脚本,阻止变更对不同自动化脚本实现层次的传导,减少了变更影响范围。有利于提高自动化脚本的可维护性。
图4 自动化测试三层实现
假设小明同学是一个已注册的常旅客,小明同学使用正确的用户 Email 和密码成功登录机票销售系统的验收测试三层模式实现过程如下。
业务规则层
文件名:UserLogin.feature
# language: zh-CN
功能:用户登录。
场景大纲: 用户登录成功。
假设:小明童鞋是一个在册的常旅客。
当小明童鞋输入正确的电子邮箱和密码
那么,小明童鞋可以成功登录他的账户。
@positive
例子:
password xiaoming@qatools.cn 12345678
业务流程层
文件名:UserLoginSteps.java
import cucumber.api.java.zh_cn.假如;
import cucumber.api.java.zh_cn.当;
import cucumber.api.java.zh_cn.那么;
public class UserLoginSteps {
@假如("^小明童鞋是一个在册的常旅客$")
public void 小明童鞋是一个在册的常旅客(String username, String password) throws Throwable {
}
@当("^小明童鞋输入正确的电子邮箱\"([^\"]*)\"和密码\"([^\"]*)\"$")
public void 小明童鞋输入正确的电子邮箱_和密码_(String username, String password) throws Throwable {
}
@Then("^小明童鞋可以成功登录他的账户$")
public void 小明童鞋可以成功登录他的账户() throws Throwable {
}
}
技术层
文件名:LoginPage.java
import net.thucydides.core.annotations.DefaultUrl;
import net.thucydides.core.pages.PageObject;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@DefaultUrl("http://www.qatools.cn/demon/ui/web/login")
public class LoginPage extends PageObject {
@FindBy(name="email")
private WebElement email;
@FindBy(name="password")
private WebElement password;
@FindBy(css = ".btn[value='Sign in']")
private WebElement signin;
public void signinAs(String userEmail, String userPassword) {
email.sendKeys(userEmail);
password.sendKeys(userPassword);
signin.click();
}
}
Selenium WebDriver 对 Web 应用中页面元素的操作过程是这样的:
- 获取 Web 应用中的目标页面元素;
- 对目标页面元素发送操作命令(事件);
- 目标页面元素对操作命令(事件)进行响应。
例如小明同学成功登录机票销售系统时,需要输入用户 Email、密码然后点击“登录”按钮登录系统。
机票销售系统用户登录页面,如下图所示:
图5 用户登录页面
用户登录页面的 HTML 代码如下:
<div class="login-box">
<form >
<h3 class="title"><span>登录</span></h3>
<label for="email" class="el-form-item__label">邮箱</label>
<input name="email" autocomplete="off" placeholder="请填写邮箱" class="el-input__inner" >
<label for="password" class="el-form-item__label">密码</label>
<input name="password" autocomplete="off" placeholder="请输入密" class="el-input__inner">
<button id="loginbtn" type="button" class="el-button el-button--primary">
<span>登录</span>
</button>
<button id="resetbtn" type="button" class="el-button el-button--default">
<span>重置</span>
</button>
</form>
</div>
Selenium WebDriver 实现自动化输入用户 Email,代码如下:
driver.findElement(By.name("email")).sendKeys("xiaoming@qatools.cn");
该页面元素操作的含义如下:
-
driver.findElement()
查找需要操作的页面元素,页面元素可以为文本框、下拉框、单选框、复选框、文本输入域等用户常操作的页面元素。也可以是其它任意一种 HTML 元素例如
<div>
、<table>
等。 -
By.name
使用 Web 元素名查找页面元素,例如 By.name("email") 查找名称为“email”页面元素,即为用户登录中的邮箱输入框。对应的 HTML 代码如下:
<input name="email" autocomplete="off" placeholder="请填写邮箱" class="el-input__inner" >
-
sendKeys("xiaoming@qatools.cn");
向已获得的页面元素发送命令,这里是发送文本“xiaoming@qatools.cn”。
上面我们提到,By.name 通过 Web 元素名定位页面元素,接下来,我们看看 Selenium WebDriver 中还有哪些元素定位方法。
Selenium WebDriver 常用元素定位方法:
- By.name():使用 Web 元素名查询定位 HTML 元素。请见下面示例。
页面元素 HTML 代码如下:
<input name="email" autocomplete="off" placeholder="请填写邮箱" class="el-input__inner" >
WebDriver 元素定位代码,如下:
driver.findElement(By.name("email")).sendKeys("xiaoming@qatools.cn");
- By.id():使用 Web 元素 ID 查询定位页面元素,示例如下:
页面元素 HTML 代码如下:
<button id="loginbtn" type="button" class="el-button el-button--primary">
<span>登录</span>
</button>
WebDriver 元素定位代码,如下:
driver.findElement(By.id("loginbtn")).click();
- By.tagName():使用 Web 元素标签名称查询定位元素,示例如下。
页面元素 HTML 代码,如下:
<button id="loginbtn" type="button" class="el-button el-button--primary">
<span>登录</span>
</button>
WebDriver 元素定位代码,如下:
driver.findElement(By.tagName("button")).click();
- By.className():使用 Web 元素的 CSS 类定位元素,示例如下:
页面元素 HTML 代码,如下:
<button id="loginbtn" type="button" class="el-button el-button--primary">
<span>登录</span>
</button>
WebDriver元素定位代码,如下:
driver.findElement(By.className(“el-button el-button--primary”)).click();
- By.linkText():使用 Web 元素超链接文本定位元素,示例如下:
页面元素 HTML 代码,如下:
<a href="http://gitbook.cn/gitchat/column/5aebe3ea4eb5f845a0773ddb#catalog" rel="nofollow">十招玩转敏捷测试</a>
WebDriver 元素定位代码,如下:
driver.findElement(By.linkText("十招玩转敏捷测试")).click();
- By.partialLinkText():使用 Web 元素的部分文本模糊定位元素,示例如下:
页面元素 HTML 代码,如下:
<a href="http://gitbook.cn/gitchat/column/5aebe3ea4eb5f845a0773ddb#catalog" rel="nofollow">十招玩转敏捷测试</a>
WebDriver 元素定位代码,如下:
driver.findElement(By..partialLinkText("敏捷测试")).click();
- By.xpath():使用页面元素在 HTML 的 DOM 路径定位元素,示例如下:
页面元素 HTML 代码,如下:
<div id="login-box" class="login-box">
<form >
<h3 class="title"><span>登录</span></h3>
<label for="email" class="el-form-item__label">邮箱</label>
<input name="email" autocomplete="off" placeholder="请填写邮箱" class="el-input__inner" >
</form>
</div>
WebDriver元素定位代码,如下:
driver.findElement(By.xpath("//*[@id='login-box']/from/input[0]")).sendKeys("xiaoming@qatools.cn");
- By.cssSelector():cssSelector 这种元素定位方式跟 xpath 比较类似,但执行速度较快,而且各种浏览器对它的支持也比较好,示例如下:
页面元素 HTML 代码,如下:
<button id="loginbtn" type="button" class="el-button el-button--primary">
<span>登录</span>
</button>
WebDriver 元素定位代码,如下:
driver.findElement(By.cssSelector(“button.el-button el-button--primary”)).click();
另外一种定位代码是:
driver.findElement(By.cssSelector(“#loginbtn”)).click();