You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
13 KiB
Markdown

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 15UI测试如何让UI测试更轻快便捷
你好,我是柳胜。
恭喜你坚持到这里,我们顺着测试金字塔底层的单元测试一步步向上,现在终于到了金字塔顶部。按照我们的整体设计,其实脏活累活已经在底层干得差不多了。
爬上塔顶不容易,应该是一身轻松,纵览风光了。可以想象,如果没有前面的整体设计,没有单元测试来夯实基础,把测试工作全都压到端到端测试,它必然会垮掉。
不过既然需要金字塔顶部这个UI测试层一定是它不可替代做得了其他层力所不能及的事儿。今天咱们就来梳理下UI测试要做什么怎么做才能收割更高的ROI。
UI全名叫做User Interface在当下User这个概念已经被扩展甚至被滥用我倒觉得UI叫做PIPeople Interface更为准确专指和人格用户交互的界面。
从UI这个角度主要有三个测试点需要去关注第一用户的行为第二UI的布局第三是用户的易用性。当然根据具体业务的需求还有其他的点比如Globalization全球化、Accessibility亲和力等等。
## 用户行为测试
用户的行为指的是用户通过操作UI获得他想要的功能。在FoodCome里用户通过WebUI填好订单信息然后点击“下订单”按钮就能完成下单功能。
![图片](https://static001.geekbang.org/resource/image/d7/81/d7cf1716877d300d129f26578a050181.png?wh=681x411)
分析一下就能知道在这个过程里有两部分代码逻辑参与了下单一个是前端逻辑就是HTML+JavaScript代码另外一个就是后端逻辑也就是我们前面讲过的RestAPI和DB。
既然后端逻辑我们在单元测试就测过了而前后端集成我们也用契约测试测过了那么UI测试的关键点就在于前端逻辑有没有又有多少
这里要分两种情况。第一种前端没有业务逻辑就是简单的发送请求接收响应并展现。这些工作都是通过浏览器的内嵌功能来完成的。比如FoodCome可以用一个HTML form来完成订单的提交过程
```xml
<form method="POST" enctype="application/x-www-form-urlencoded" action="/html/codes/html_form_handler.cfm">
<p>
<label>餐馆名字
<input type="text" name="restaurant_name" required>
</label> 
</p>
<fieldset>
<legend>菜单</legend>
<p><label> <input type="checkbox" value="no1"> 宫保鸡丁 </label></p>
<p><label> <input type="checkbox" value="no2"> 佛跳墙 </label></p>
<p><label> <input type="checkbox" value="no3"> 珍珠翡翠白玉汤 </label></p>
</fieldset>
<p><button>下订单</button></p>
</form>
```
这也是业界提到过的Thin Client瘦客户端客户端里没有或只有很少的业务逻辑。
瘦客户端怎么测我的答案是在单元测试和集成测试已经充分的情况下瘦客户端只需找两三个典型业务场景测一下甚至都不需要考虑UI自动化。因为主要的逻辑和路径都已经测过了嘛你没必要再花时间重复。
与瘦客户端相对应的是胖客户端也叫Rich Client当然胖客户端里是嵌入了大量的业务逻辑。当今业界胖客户端更加普遍比如WebUI里嵌入了JavaScript来聚合后端的数据、画图、表格、排序等等从这个角度你也可以把Desktop客户端直接当作胖客户端来对待。
胖客户端该怎么测? 要回答这个问题,我们需要首先思考一下“胖客户端是什么”。在微服务世界里,每个微服务实现自己的业务逻辑,向外提供服务,同时也是客户端去消费其他的服务。
而胖客户端是什么呢,它也有自己的业务逻辑,聚合数据、图形化都是它的业务逻辑。但是有一点特殊,胖客户端是微服务集群调用链条最早一个,它只会去调用别人,调用胖客户端的是终端用户。
从这个角度来看,胖客户端满足了提供服务,也消费其他服务的微服务特征,因此**胖客户端本质上也是一个微服务。**
说到这里胖客户端怎么测这个问题的答案就呼之欲出了。微服务该怎么测胖客户端就该怎么测。什么意思呢你还是要遵循测试3KU金字塔原则胖客户端首先要做单元测试再做集成测试最后才是UI测试。
看到这里你可能会有点困惑UI客户端还要分层这是怎么回事呢我拿WebUI的开发框架举个例子你就明白了。
React是业界很常用的JavaScript开发框架看看它是怎么实现下订单操作的
```javascript
class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleChange(event) {    this.setState({value: event.target.value});  }
  handleSubmit(event) {
    alert('Your order is: ' + this.state.value);
    event.preventDefault();
  }
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite flavor:
          <select value={this.state.value} onChange={this.handleChange}>           
<option value="no1">宫保鸡丁</option>
            <option value="no2">佛跳墙</option>
            <option value="no3">珍珠翡翠白玉汤</option>
          </select>
        </label>
        <input type="submit" value="下订单" />
      </form>
    );
  }
}
```
上面一段简单React JavaScript代码有render来做数据的展现有Handler来做数据的处理还有props做数据的存储。其实React开发的前端功能跟一个后端服务的MVC结构是类似的。
![图片](https://static001.geekbang.org/resource/image/0c/13/0c19d928a328cf566yyb180295e53b13.jpg?wh=1920x1242)
看到没有UI前端也有设计模式也可以实现很多业务逻辑。所以你可以把UI前端当做一个微服务来测试既然是微服务那就可以分层做单元测试。
那前端的单元测试怎么做呢和后端原理是一样的该写Test方法写Test方法该Assert就Assert该Mock就Mock。只是前端开发框架有很多种相对应地单元测试框架也有多种你需要找到匹配的那一对。我给你总结了一个表格你也可以结合自己实践拓展、丰富它。
![图片](https://static001.geekbang.org/resource/image/cb/77/cb48a99e45156d32cedc40ddc7410b77.jpg?wh=1920x714)
下面是使用Vue Test Utils来执行单元测试的例子在订单页面点击一个check Order按钮验证页面上是否会显示“order validated”的消息。
```javascript
import { shallowMount } from '@vue/test-utils'
import OrderToggle from '@/components/OrderToggle.vue'
import Order from '@/components/Order.vue'
describe('OrderToggle.vue', () => {
it('toggles msg passed to Order when Place Order button is clicked', () => {
const wrapper = shallowMount(OrderToggle)
const button = wrapper.find('#check-order')
button.trigger('click')
const OrderComponent = wrapper.find(Order)
expect(OrderComponent.props()).toEqual({msg: 'order validated'})
})
})
```
你可以看到JavaScript单元测试能测试数据逻辑也能测试页面事件模拟人的行为发送一个个点击、输入事件。那么你可能还想问前端JavaScript的单元测试做完是不是就不需要额外的UI测试了呢
这是一个好问题不过完成之上我们希望做得更加完美、更有效益。结合我们专栏里我不厌其烦给你提到的3KU原则本着“做有效的不做浪费的测试”的目标单元测试做完了UI上只做单元测试没做到的事情。
你可以思考一下符合这个条件的场景有没有,在哪里?
## 页面的Layout布局测试
相比API测试UI的测试还有一个特殊的地方不但要验证UI的控件画出来了而且还要验证它们都在正确的位置上这个验证就叫做UI布局测试。
你可以这样理解API测试里我们检查数据的时候是一维的检查而在UI测试里数据的检查是二维的有了x、y的坐标。这个复杂度一下子就上来了。
布局测试怎么做?有两种方案,咱们分别来看看。
一种是抓图方案它是在运行UI自动化测试的时候顺便调用captureScreen函数对当前的UI抓屏保存成图片。然后利用图片比较技术去看页面的布局有没有发生变化。所以这个方案的技术关键点就是**位图比较**。业界比较成熟的技术实现有Applitools、Sikuli。
比如用Applitools的eyes类进行对比
```java
driver = new ChromeDriver();
eyes = new CompareEyes();
// 设置匹配级别为Layout布局
eyes.setMatchLevel(MatchLevel.LAYOUT);
eyes.setEnableComparison(true);
eyes.open(driver, appName, testName, viewPortSize);
eyes.switchToComparisonMode(driver);
// 使用eyes对比当前窗口和已经保存的图片
eyes.check("/Users/sheng/Desktop/login.png", Target.window());
eyes.close();
driver.quit();
```
上面的代码是启动Selenium Web Driver加载页面初始化eyes然后调用eyes的check函数来实现图片的比较。
Applitools有AI的功能在早期测试人员手工地对它的比对结果进行确认或纠正这相当于是训练了AI比对模型。这样使用一段时间后它的比对会越来越智能结果会越来越准确。
第二种是Layout规格说明书方案什么意思呢跟传统测试一样需要先写一份Layout规格说明书比如屏幕上在什么位置应该出现什么元素等等应该有一个列表展示。
然后自动化测试运行的时候就把render出来的页面和规格说明书相比较测试成功或失败。在这个领域里的工具也有很多种Galen和Lineup是其中的代表。
比如说下面我用Galen这个工具演示的FoodCome系统login页面的Layout规格说明书LoginPage.spec
```java
@objects
login-box id login-page
login-caption css #login-page h2
username-textfield css input[name='login.username']
password-textfield css input[name='login.password']
login-button css .button-login
cancel-button css .button-cancel
= Login box =
@on *
login-box:
centered horizontally inside content 1px
below menu 20 to 45px
login-caption:
height 20 to 35px
text is "Login"
username-textfield, password-textfield:
height 25 to 35 px
login-button, cancel-button:
height 40 to 50 px
```
在这个spec里描述了登录页面的布局有6个页面对象登录框、登录标题、用户名输入框密码输入框、登录按钮和取消按钮还说明了它们各自的样式和位置。
在运行测试的时候当加载login页面的时候会把展现出来的页面和LoginPage.spec进行匹配验证匹配成功说明Layout是按照预期加载的。
```java
public void loginPage_shouldLookGood_onDevice(TestDevice device) throws IOException {
load("/");
getDriver().findElement(By.xpath("//button[.='Login']")).click();
checkLayout("/specs/loginPage.spec", device.getTags())
}
```
## 小结
到这里,总结一下我们今天学习到的内容。
UI测试主要有三个关注点第一用户的行为第二UI的布局第三用户的易用性。
我并没有在正文介绍易用性是因为这个关注点最终指向的问题是用户体验是一个“感觉好还是坏”。这是一个通过计算机技术很难做回答的问题。所以用户体验还是手工测试的方法你可以考虑用探索性测试的策略来去发现易用性的问题而这一讲我们重点讨论了前两个关注点用户的行为和UI的布局。
从用户行为这个视角分析UI测试的客户端可以分为瘦客户端和胖客户端瘦客户端的测试简单你可以按照Happy Path的思路找出一两个案例来跑一下就可以了。而胖客户端包含了大量的业务逻辑你应该用测试服务的方法来测试胖客户端也要做单元测试。这一讲中针对JavaScript开发框架列出了相应的单元测试框架供你参考。
UI的布局测试也是一个特殊的领域业界里有两种自动化思路一个是基于图片一个是基于Spec两种方法都各有优势和劣势。我们可以根据项目目标和具体情况采用其中一个也可以把这两个思路都用上。
## 牛刀小试
说说你的项目中UI前端有没有做单元测试
欢迎你在留言区跟我交流互动,也推荐你把这讲内容分享给更多同事、朋友。