写点什么

视觉感知测试

  • 2014-06-20
  • 本文字数:8050 字

    阅读完需:约 26 分钟

随着互联网第二春的到来以及 Web2.0 的盛行,Web 应用程序开发已经成为了当前软件开发的主力军。现在无论是企业级应用,社交应用还是移动应用,Web 已经成为标准配置,而且很多企业正在逐步的将自己的企业级本地应用进行互联网 Web 化。但是 Web 的界面布局测试,多浏览器测试,CSS/JavsScript 的重构等都成为界面测试的痛中之痛,特别是大型 Web 应用的回归测试量太大,从而导致回归测试很多时候根本无法完成,所以很少会有团队能完成全方位的界面布局回归测试,特别是对于使用 Agile 流程开发的团队就更加困难。

而且现在大家对用户体验以及持续部署越来越重视,导致 Web 应用程序的界面开发和测试难上加难。

首先来看看 Web 界面开发和测试为什么如此困难。

1,Web 界面布局回归测试

对于 Web 网页界面布局测试一般都是由人工手动对比设计图和产品界面。而人工对比测试存在两个问题:a, 速度慢;b, 人的不确定性。对于拥有大量复杂界面的企业级 Web 应用,界面布局的回归测试的数量巨大,再加上这两个问题,导致这类应用的界面布局回归测试时间很长,成本很高,所以很多基于 Agile 项目基本不可能在迭代周期内高质量的完成其界面回归测试。对于每天做一次回归,那更是不可能完成的任务。

(下面有一个游戏“大家来找茬”,请读者用心找找有多少处不同,并记录一下用了多少时间。答案在附 1 图中)

图 1,大家来找茬

2,CSS/JavaScript 代码重构

现在 Web 前端越来越复杂,所以代码量也急速增加,导致前端开发像后端开发一样开始使用基于 Library, Module 和 Pattern 的开发方式。从而产生了一个问题:当有公共代码被修改和重构之后,如何快速发现界面的 side effect?

由于 CSS 和控制界面的 JavaScript 代码被重构之后,只能通过人眼手动检测其正确性,导致开发和测试人员很难在有限的时间找到所有被修改的代码影响到的界面进行检查。最后很可能会有一些 side effect 在开发和测试阶段都不被发现而进入产品环境。

3,多浏览器

Web 应用其最大的优势就是其可以跨平台跨浏览器,使用者可以在不同的操作系统中使用不同的浏览器访问并使用 Web 应用。但是这个优势也带来了很大的问题:需要做大量的浏览器兼容性测试。而被测浏览器的数量越多(现在的主流浏览器包括 IE, Chrome, Firefox, Safari 等,并且每种浏览器还有很多种版本),测试数量和时间也会成倍增长。这个痛也导致很多大型 Web 应用基本上很难在限定时间内完成大部分主流浏览器的兼容性测试。如果一定要做,那么也需要付出巨大的成本,比如添加更多的测试人员。

4,响应式设计 (Responsive Web Design) 测试

由于移动设备的普及,导致大量的用户使用手机或者平板使用 Web 应用。由于移动设备拥有各种各样的分辨率,因此设计人员也开始考虑针对不同的分辨率设计应用界面,响应式设计 (Responsive Web Design) 也孕育而生。但是响应式设计很难测试,基本上只能靠手工进行,而且还需要准备各种分辨率的设备或者各种分辨率的浏览器。需要测试的分辨率越多,测试的时间越长,成本就也越高。

下面有两张真实网页的截图,其中有很多不同之处,读者可以尝试再找一下有多少。答案在附 2 图中。

图 2,网页 1

图 3,网页 2

什么是视觉感知测试 -Perceptual Testing

对于界面布局,传统的测试都是由人工对比设计图和产品界面。当界面有修改之后,再由人通过肉眼去检查修改 (包括正确的和错误的修改),这样即费时而且测试结果又不稳定,因为人是有情绪的。但是我们认为如果一个界面通过第一次的人工验证并发布之后,它就是一个正确的标准界面,并且是包含了人工测试价值的资产。当下一次测试的时候,这部分价值就应该被保留并重用起来,用于减少新的一次测试的时间,从而实现界面的快速回归测试。

为了解决上面提到的各种问题,视觉感知测试孕育而生。它使用传统的对图片进行二进制比较的办法,结合敏捷迭代开发的理念,产生的一种针对界面布局的自动化测试方法。

视觉感知测试包含以下几个主要的测试步骤:

  1. 对于产品版本进行截图(产品线上环境或者类产品环境) 首先人工完成第一个软件版本的测试并部署上线,在第二个版本需要进行测试的时候首先对第一个版本的所有界面进行截图。
  2. 对于新的发行版进行截图(比如 staging 环境) 然后对第二个需要进行测试的版本的所有界面也进行截图。
  3. 配对 URL(忽略 hostname) 通过配对 URL,对所有的截图按照相同的 URL 进行分组。当然有时候会出现新的界面,有时候老的界面会被删除。对于新的界面就需要人工进行首次验证测试 。
  4. 像素级别的图形比较 对于分组之后的截图进行像素级别的比较并生产差别图。有时候为了降噪,可以只对局部关心的组件进行比较。
  5. 人工查看所有不同 最后通过人工审查差别图报告完成测试。

视觉感知测试的一些实例

1,CSS 的改变

对于开发人员,CSS 改变之后的 side effect 是最头痛的事情,下面展示了当 CSS 改版之后页面的变化

图 4,CSS 的改变

2,内容的改变

对于测试人员,大量复杂页面的微小修改很难发现的,下面展示了如果使用视觉比较找到差异。

图 5,内容的改变 1

图 6,内容的改变 2

3,事件处理的改变

对于测试人员,有些界面需要鼠标点击或者悬停才能展现出来。而对于这样的界面的测试就必须人工来做。下面展示了一个选择框在鼠标点击之后在两个版本之间产生的差异。

图 7,事件处理的改变

4,响应式设计

对于开发和测试人员,如果要测试响应式设计就必须使用不同分辨率的设备,模拟器或者调整浏览器到各种分辨率,这将是一个费时费力费钱的工作。下面展示了如果视觉比较如果检查响应式设计。

图 8,响应式设计

视觉对比 - UI 自动化 end-to-end 测试的最后一公里

持续交付中的视觉感知测试

下图为传统的持续交付流程:

图 9,没有视觉感知的持续交付

下图为加入了视觉感知测试的持续交付流程,其中主要的区别就是部署之前要并行与其他自动化测试做一次视觉感知测试。

图 10,包含视觉感知测试的持续交付

下图为实施了视觉感知测试之后对于界面回归测试的时间示意图

图 11,界面回归测试时间示意图

三个视觉感知测试工具

1,Mogotest

  • 来自 Mogotest,基于“云”
  • Restful API 开发测试
  • 支持多浏览器
  • 不支持本地化
  • 商用
  • http://mogotest.com/

Mogotest 是一个商用的产品,它提供一个“云”测试平台,可以让用户在其平台上使用各种不同的浏览器访问被测试页面,并进行对比。主要目的是测试不同浏览器之间的兼容性,不能测试动态页面等。

2,DPXDT

Dpxdt 是基于 Python 和 PhantomJS 开发的一个 Web Service 系统,其中 PhantomJS 可以理解为一个没有界面的浏览器。用户使用其提供的 RESTFul API 可以十分方便的对比两个页面,而且它还提供一个功能十分强大的报表系统。对于全部是静态页面的 Web 系统来说非常适用,不过对于需要手动导航,比如需要进行输入,点击或者鼠标悬停等操作之后才能进行检测的界面,它默认并不支持,需要对其本身进行修改才可以。不过它还提供了一个方式可以把他很方便的部署到 GWS 上。

3,Viff

  • 来自 ThoughtWorks,基于 Javascript/node
  • 支持多浏览器(Selenium WebDriver)
  • JavaScript/DSL 开发测试
  • 支持人工认证报表系统
  • 开源,免费
  • 支持嵌入式测试:智能电视,手机等
  • https://github.com/winsonwq/viff

Viff 是基于 NodeJS 和 Selenium 开发的一个本地工具。通过编写 JavaScript 代码来调用 Selenium API, 并在真实的浏览器中进行截图比较。所以它比较适合动态的 Web 系统,因为可以编写代码模拟用户输入和点击操作。由于它底层使用的是 Selenium 作为驱动,所以他支持多种浏览器,比如 IE,Chrome,Firefox 等。由于最新的 Selenium 加入了对 Android 和 iOS 的支持,因而 Viff 也能够支持 Android 和 iOS 上的浏览器测试。

如果对你来说搭建多浏览器环境比较困难,比如需要同时测试 IE8,IE9,IE10 等,可以选择 BrowserStack。BrowserStack 是一个商业产品,他同时通过 Web 界面和 API 接口提供多浏览器环境给客户进行 Web 测试,Viff 可以使用其 API 进行进行多浏览器截图。对于 Viff,由于编写 JavaScript 代码也需要一定的门槛,所以对于没有代码能力的使用者在测试静态网页的时候应该选择 Dpxdt,但是如果你有一定的代码能力,并且希望能在当前的功能测试里面加上视觉感知测试或者希望对局部的界面进行测试,建议选用 Viff。现在 Viff 正在开发 Web Service 功能,这样以后就可以作为一个 Service 进行部署和使用。

还有其他的视觉感知测试工具,这里就不一一熬述了。在 VIFF 的官网上有一张多个工具的比较图,有兴趣的读者可以参考一下: http://twers.github.io/Viff-Service/

VIFF 演示

1,安装

VIFF 的安装步骤请参考其项目上的说明文档 https://github.com/winsonwq/viff

为了帮助大家理解和学习 VIFF,我们还开发了一系列的 Examples 和 Demos,请参见 https://github.com/winsonwq/viff-examples ,以下所有代码全部来自这个项目。

2, 安装需要被测试的演示网站

下载 https://github.com/winsonwq/viff-examples 上的代码,模拟产品版本的站点在 viff-examples/example/prod 里面,模拟需要测试的站点在 viff-examples/example/build 里面。由于演示网站都是静态代码,所以用任意一个 HTTP Sever 进行部署都可以,比如我使用 Nginx 将其部署在本地的 8000 端口上。部署成功之后,通过 http://localhost:8000/example/build http://localhost:8000/example/prod 就可以访问到两个测试演示站点。

3, 使用 VIFF 的 Main API 进行测试

对于一个全新的项目,直接使用 VIFF 的 Main API 编写测试代码,如下:

复制代码
'use strict'
var config = module.exports = {
seleniumHost: 'http://localhost:4444/wd/hub',
browsers: ['firefox'],
envHosts: {
build: 'http://localhost:8000/example/build',
prod: 'http://localhost:8000/example/prod'
},
paths: [],
reportFormat: 'file',
test: function test (description, caseConfig) {
var c = {};
c[description] = caseConfig;
this.paths.push(c);
}
};
config.test('Home Page', ['/github.html', function (browser) {
return browser.waitForElementByCssSelector('.repo-list-item', browser.isDisplayed());
}]);
config.test('Search Result', ['/github.html', function (browser) {
return browser
.waitForElementByCssSelector('.repo-list-item', browser.isDisplayed())
.elementByCssSelector('[type="search"]').type('commander.js')
.sleep(1000);
}]);
config.test('Open Readme file', ['/github.html', function (browser) {
return browser
.waitForElementByCssSelector('.repo-list-item', browser.isDisplayed())
.elementByCssSelector('.repo-list-item:nth-child(2)').click()
.waitForElementByCssSelector('.repo-readme', browser.isDisplayed());
}])

测试结果报表如下:

1,首先测试主页,由于没有任何改动,所以测试结果是绿色,表示产品环境和测试环境没有任何改变。

图 12,演示结果报表 1

2,然后在搜索输入框中输入 commander.js,结果发现产品版本上的 show 1 repository 在测试版本上变成了 showing 1 repositories,所以测试结果是红色。然后通过报表可以在立即发现改变,然后在进行人工审核其修改的正确性。这里明显是测试版本出现了错误,然后针对这个错误就可以上报一个 bug 了。

图 13,演示结果报表 2

3,清空输入框,然后在主页中点击 co,然后测试结果还是红色。认真仔细的查看才发现 generator 在测试版本中被改成了 generators。在如此多的内容中找到一个字母 s 的改变是非常困难的,但是通过 VIFF 的测试报告,又一次快速的轻松地发现了改变。

图 14,演示结果报表 3

4,使用 VIFF 的 Client API 进行测试

对于一个已经有 Functional Testing 的项目,可以不需要重新开发 VIFF 测试代码,只需要在功能测试代码中调用其 Client API,同样可以完成视觉感知测试。由于当前 VIFF 只开发了 JavaScript 版本的 Client API,所以下面的例子使用的是基于 JavaScript 开发的 Functional Testing。以后 VIFF 会初步提供基于其他语言的 Client API,比如 Java,Python 和 Ruby 等。

(代码说明,其中两行绿色代码表示分别对产品环境和测试环境进行 Functional Testing;其中所有的蓝色代码是 Functional Testing 的代码;其中所有红色部分代码是 VIFF 的 Client API)

复制代码
var wd = require('wd');
var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
chai.should();
var ViffClient = require('viff-client');
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
describe('Github Page Test', function() {
this.timeout(100000);
var browser, buildScreenshot, prodScreenshot, buildClient, prodClient;
before(function (done) {
browser = wd.promiseChainRemote('http://localhost:4444/wd/hub');
browser.init({ browserName: 'firefox' }).nodeify(done);
buildClient = new ViffClient('http://localhost:3000', {
name: 'build',
host: 'http://localhost:8000/example/build/github.html',
capabilities: 'firefox'
});
buildScreenshot = prepareTakeScreenshot(browser, buildClient);
prodClient = new ViffClient('http://localhost:3000', {
name: 'prod',
host: 'http://localhost:8000/example/prod/github.html',
capabilities: 'firefox'
});
prodScreenshot = prepareTakeScreenshot(browser, prodClient);
});
after(function (done) {
buildClient.generateReport(function () {
browser.quit().nodeify(done);
});
});
<span color="#00ff00">describe('in build environment', function() {</span>
beforeEach(function(done) {
browser
.get("http://localhost:8000/example/build/github.html")
.waitForElementByCssSelector('.repo-list-item', browser.isDisplayed())
.nodeify(done);
});
<span color="#0000ff">it('could go to Home Page', function(done) {</span>
browser.title().should.become("TJ Holowaychuk's Github Repositories")
.then(function () {
<span color="#ff0000">buildScreenshot({ 'Home Page': ['/github.html'] }, 'screenshots/homepage.png', done);</span>
});
});
<span color="#0000ff">it('should filter repo as per keyword', function(done) {</span>
browser
.elementByCssSelector('[type="search"]').type('commander.js')
.sleep(1000)
.elementsByCssSelector('.repo-list-item')
.then(function (elements) {
elements.length.should.eql(1);
<span color="#ff0000"> buildScreenshot({ 'Search Result': ['/github.html'] }, 'screenshots/filter.png', done);</span>
});
});
<span color="#0000ff">it('should open readme file', function(done) {</span>
browser
.elementByCssSelector('.repo-list-item:nth-child(2)').click()
.waitForElementByCssSelector('.repo-readme', browser.isDisplayed())
.elementByCssSelector('.repo-readme').text().should.eventually.contain("# Co")
.then(function () {
<span color="#ff0000">buildScreenshot({ 'Should open readme file': ['/github.html'] }, 'screenshots/github.png', done);</span>
});
});
});
<span color="#00ff00">describe('in prod environment', function() { </span> beforeEach(function(done) {
browser
.get("http://localhost:8000/example/prod/github.html")
.waitForElementByCssSelector('.repo-list-item', browser.isDisplayed())
.nodeify(done);
});
<span color="#0000ff">it('could go to Home Page', function(done) {</span>
browser.title().should.become("TJ Holowaychuk's Github Repositories").then(function () {
<span color="#ff0000">prodScreenshot({ 'Home Page': ['/github.html'] }, 'screenshots/homepage.png', done); </span><span color="#000000"> });</span>
});
<span color="#0000ff"> it('should filter repo as per keyword', function(done) {</span>
browser.elementByCssSelector('[type="search"]').type('commander.js')
.sleep(1000)
.elementsByCssSelector('.repo-list-item')
.then(function (elements) {
elements.length.should.eql(1);
<span color="#ff0000">prodScreenshot({ 'Search Result': ['/github.html'] }, 'screenshots/filter.png', done);</span>
});
});
<span color="#0000ff">it('should open readme file', function(done) {</span>
browser
.elementByCssSelector('.repo-list-item:nth-child(2)').click()
.waitForElementByCssSelector('.repo-readme', browser.isDisplayed())
.elementByCssSelector('.repo-readme').text().should.eventually.contain("# Co")
.then(function () {
<span color="#ff0000">prodScreenshot({ 'Should open readme file': ['/github.html'] }, 'screenshots/github.png', done); </span> });
});
});
});
function prepareTakeScreenshot(browser, viffClient) {
return function (url, imagePath, callback) {
browser
.saveScreenshot(imagePath)
.then(function () {
viffClient.post(url, imagePath, callback);
});
};
}

测试结果报表同第 2 步一样,见图 12,图 13 和图 14

5,使用 VIFF 对移动 Web 进行测试

由于 VIFF 使用的 Selenium,而 Selenium 本身支持移动 Web 支持,所以 VIFF 与生俱来就包含此功能。比如现在要对 iPhone 进行测试,只需要将第 2 步 Demo 代码中的 firefox 改成 iPhone 就可以了。(如果对 Selenium 测试 iPhone 的测试环境有疑问,请参见 Selenium 官方文档和 Selenium iPhoneDriver: https://code.google.com/p/selenium/wiki/IPhoneDriver)

视觉感知测试的几个主要使用场景

  1. Web 应用界面的回归测试
  2. 智能嵌入式应用界面的回归测试 - 如智能电视应用,智能手机应用
  3. 大数据呈现的回归测试

总结

视觉感知测试是近几年为了解决大量繁重的人工界面回归测试才出现的一种自动化测试方法。它不仅能帮助测试人员进行界面回归测试,而且还能帮助开发人员在重构或修改公共 UI 代码的时候快速进行 side effect 检查,从而大大减少了测试的时间,并且使得对大量的界面进行回归测试成为了现实,最终增加了软件的质量,特别是最大可能的保证了软件的用户体验。希望在不久的将来,越来越多的复杂界面系统将会使用视觉感知测试来快速完成其全面的回归测试,从而实现真正的高质量的快速持续交付。

附 1:

图 15,大家来找茬结果

图 16,网页对比结果

作者简介

刘冉,现任 ThoughtWorks 高级软件质量咨询师, 超过 10 年软件开发和测试工作工作经验。最熟悉的领域是嵌入式系统开发、Linux 系统开发、各种脚本、各种测试工具、各种自动化测试系统开发、以及 Agile 中的 QA。其中对于服务器性能测试,Web 功能测试,以及测试分层一体化解决方案有较深的理解。现在关注于全方位自动化 QA 的工作,以及对于 Agile 流程中怎么实现统一的流程、故事、功能、测试和文档管理,以及质量控制度量。


感谢张逸对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014-06-20 00:178711

评论

发布
暂无评论
发现更多内容

【新布局】火绒安全企业产品Linux终端、macOS终端开启公测

火绒安全

macos Linux 服务器 终端安全 Windows Server

后端开发—一文详解网络IO模型

Linux服务器开发

reactor 后端开发 Linux服务器开发 网络io 网络模型

下拉推荐在 Shopee Chatbot 中的探索和实践

Shopee技术团队

算法 chatbot 推荐算法

OpenHarmony标准设备应用开发(三)——分布式数据管理

OpenHarmony开发者

OpenHarmony 分布式数据

昇思MindSpore行至2022,开源社区成就生态共赢

这不科技

华为 昇思MindSpore

春暖花开,等你而来!4月月更挑战开始啦!

InfoQ写作社区官方

热门活动 4月月更

资产动态管理系统解决方案

低代码小观

资产管理 企业管理系统 CRM系统 客户关系管理系统 资产安全

龙蜥开发者说:聊一聊我技术生涯的“三次迭代” | 第 3 期

OpenAnolis小助手

技术分享 开发者故事 龙蜥开发者说 突出贡献奖

Git教程-帮助开发人员更好的运用Git | 云效

阿里云云效

git 云计算 阿里云 DevOps 开发者

Tapdata 肖贝贝:实时数据引擎系列(六)-从 PostgreSQL 实时数据集成看增量数据缓存层的必要性

tapdata

数据库 实时数据

汉化版postman

Liam

Jmeter Postman 接口测试 API swagger

外部数据的合规引入助力银行用户营销系统冷启动

易观分析

隐私计算

#JiraHero:Soumen Deb——重塑 Jira Software 中的 Bug 工作流,提高可见性、简化开发流程

龙智—DevSecOps解决方案

Atlassian Jira

OpenHarmony标准设备应用开发(二)——布局、动画与音乐

OpenHarmony开发者

动画 OpenHarmony 音乐播放

Microchip推出模拟嵌入式SuperFlash技术解决边缘语音处理难题

Geek_2d6073

产品FAQ(常见问题)文档模版

小炮

产品 FAQ

华为云GaussDB专家走进课堂,跟莘莘学子聊聊数据库

华为云数据库小助手

GaussDB GaussDB(for openGauss) GaussDB(for MySQL)

软件定义存储厂商大道云行加入龙蜥社区

OpenAnolis小助手

生态 存储技术 龙蜥社区 大道云行 CLA

week4作业

Asha

向工程腐化开炮 | 治理思路全解

阿里巴巴终端技术

Java android 腐化治理 工程腐化

适合 Kubernetes 初学者的一些实战练习 (三)

汪子熙

云原生 集群 Kubernetes 集群 Kubernetes, 云原生, eBPF 3月月更

Rust Cell 与RefCell,有啥区别?

非凸科技

深入垂直业务场景,SaaS版供应商业务协同管理系统促进企业与供应商高效协同

数商云

数字化转型 供应链系统

知识文档管理系统:帮助企业管理文档

小炮

知识管理 文档管理

java版gRPC实战之一:用proto生成代码

程序员欣宸

Java gRPC

教你VUE中的filters过滤器2种用法

华为云开发者联盟

Vue 过滤器 filters过滤器 组件过滤器 全局过滤器

小程序开发入门教程

CRMEB

实战天翼云云主机系统盘扩容

天翼云开发者社区

云主机

利用 IoTDB 替换 OpenTSDB,服务大唐集团60家电厂,减少95%运维成本

Apache IoTDB

Apache IoTDB

叮咚!参与微服务免费试用,有机会获得腾讯内推资格!

InfoQ写作社区官方

腾讯云 微服务 热门活动

一文带你了解 Python 中的迭代器

踏雪痕

Python 3月程序媛福利 3月月更

视觉感知测试_软件工程_刘冉_InfoQ精选文章