级别: 中级 赵 森, 软件工程师, IBM 梁佩佩, 软件工程师, IBM
2009 年 7 月 30 日
在测试的过程中,我们经常要通过图片对比确认测试的结果。对于手工测试来说,这是个很简单的事情,但是,对于自动化测试,不同的平台、不同的机器分辨率、不同的图像展示工具、不同的图片存储格式,使得自动化测试中的图片对比变得困难。新一版的 Rational Functional Tester V8.0(RFT8.0)工具提供了专门用于图片验证的功能,以实现图形界面验证的自动化检测。本文将介绍一种使用 RFT 基于结果集的比对方式,以实现自动化图片比对。
Rational Functional Tester V8.0 的图片验证
新一版的 Rational Functional Tester V8.0(RFT8.0)工具提供了专门用于图片验证的功能,以实现图形界面验证的自动化检测。RFT8.0 的这一功能采用了屏幕截取方式并将结果以无损压缩的 PNG 格式保存。
使用屏幕截取的方式很便捷,但是不可避免地会与测试环境紧密相连。不同测试环境的分辨率、系统使用的颜色质量等因素都不完全一致,因此同一个验证点在另一个测试环境中通过的几率很低。这样的情况使得测试脚本的移植性较差,而对于自动化测试来说,测试脚本的可移植性是很重要的。
下面章节中介绍的基于结果集的图片比对方式,将结合 RFT8 图片的优势并对补充其不足之处。
影响图片对比的因素
我们先来分析一下影响图片对比的因素。
影响图片对比的因素主要有:分辨率大小、操作系统、字符集、显示图片的软件、图片的存储格式等。
表 1. 影响图片对比的因素
|
种类
|
说明
| |
1. 分辨率大小
|
比如 1024x768 和 1280x1024 两个分辨率下屏幕截取图片的像数点一定不相等
| |
2. 操作系统
|
有些软件的 GUI 界面是相对大小,而非固定大小,例如 button 控件在 Linux 和 windows 下的截图很可能大小不等
| |
3. 字符集
|
例如 GB2312 和 EN 的字符在屏幕上显示的大小很可能不等
| |
4. 显示图片的软件
|
比如一副非固定尺寸的图片在 IE 和 Fire Fox 中的显示大小很可能不等,还可能因为 IE 和 Fire Fox 的布局不同而不同(例如 CSS 中以百分比来布局整个页面)。
| |
5. 图片的存储
|
图片的存储类型和每台测试机的编码解码器将影响图片的对比
|
上述的这些因素,导致了单纯的屏幕截取比对的不稳定性和低移植性。
在同一台测试机上在屏幕截取一副图片作为验证图片,当第二次运行这个图片验证点时,实际得到的图片,与刚刚截取的验证图片对比可能成功,也可能失败。
屏幕截图与测试机器的显示器有关,而显示器的颜色的显示很有可能受到硬件和外来因素变化(比如磁场)的影响,因此产生的图片比较容易失真。但是这种失真在大部分情况下我们肉眼很难看出,但 RFT8 却能精确比对每个像数点的坐标、颜色等等。由于颜色值失真会有细微的差别,我们可以认为不可重现性的原因主要是由于颜色失真产生的,并且在大部分情况下失真的程度很小。
脚本只能在一台机器上运行以保证执行的成功,这种局限性在很多情况下是难以被接受的。
由于 RFT 的图片验证是严格的逐个像数点比对,而在一台机器上截取的图片的像数点个数等于在另一台机器上截取的同一显示页面图片的像数点个数的可能性很小,除非保持这两台机器包括软硬件的环境的完全一致,但这是非常困难的。而且如果要测试的产品需要覆盖不同的操作系统、不同的浏览器和不同的测试语言时,情况就更为复杂了。
虽然对于不同的测试环境影响图片对比的因素有所不同,但总的来说,这是一个有限的集合,根据可重现性和可移植性对这些因素可分类为:
表 2. 影响图片对比的因素的分类
|
因素
|
分类
| |
颜色失真
|
不可重现
| |
分辨率大小
|
不可移植(像数点数量不同)
| |
操作系统
|
不可移植(像数点数量不同)
| |
字符集
|
不可移植(像数点数量不同)
| |
显示图片的软件
|
不可移植(像数点数量不同)
| |
图片的存储
|
不可移植(存储格式,编码解法不同)
|
基于结果集的图片比对原理
针对上述的分析,如果颜色失真是造成图片对比不可重现的主要原因,我们可以采用色差分析解决。
而对于可移植性,我们需要选择合适的图片存储格式并处理像素点数量不等的问题。
由于软硬件对截取的图片对比产生影响,因此我们需要选择一种与软硬件无关,并且无损压缩的图片格式。当前具备这两种特性的图片存储格式只有 PNG。
没经过压缩处理的图片的大小一般都在 4-10M 之间,当图片数量达到一定数量时将不利于维护,因此需要使用统一的压缩比例。压缩处理后的图片的大小在 10-15K 之间比较合适。
在比对结果时,我们需要截取屏幕图像保存为文件并与基准图片进行比较。在截取当前屏幕并保存为文件的这个过程中,需要通过编码器的处理。但是不同的机器,其编码器不一定一样,而编码器的不一致将会导致解码后的结果不一致。也就是相同的图片经过不同编码器的处理,比对结果可能失败。因此,我们需要使用统一的编码器。
造成同一截图在不同测试环境下的像素点不同的主要影响因素是:分辨率大小,操作系统,字符集,显示图片的软件。
假设我们的测试需要覆盖以下环境 – n 种分辨率,m 种操作系统,x 种字符集,y 种图片显示软件。那么同一个画面在不同测试环境下的屏幕截图数为
Total 截图数 = n*m*x*y
如果需要覆盖所有的测试环境,我们就要建立一个期望的结果集合,这个集合中有 n*m*x*y 幅图片。
当在某一种测试环境中执行时,我们先截取当前屏幕保存为图片,而后依据测试环境的参数从期望结果集合中取一副对应的图片进行对比。如果对比成功,证明这个截图符合预期,反之,和预期结果不符合。
这种方法的核心就是结果集合的建立。但是如果结果集合过大,需要较大的维护力度;如果结果集过小,某些环境不能被覆盖,对比将会失败。
基于结果集的图片验证的实现
结果集的建立
前面我们提到,如果要覆盖更多不同的测试环境,我们就要扩大结果集。但是手动去建立所有期望的图片是一个很费时的工作。下面我们就来介绍建立结果集的方法。
首先我们从测试产品覆盖的不同测试环境中选取影响截图变化的元素。
一般情况下,我们认为有四种因素:n 种分辨率,m 种操作系统,x 种字符集,y 种界面显示软件,从而得出期望结果集合的全集为 n*m*x*y。但是,在现实中,我们的测试集合其实是这个集合的一个子集,可能随着产品不断的完善、稳定和扩展,慢慢的向全集靠拢。因此,我们的结果集的建立应该是一种动态的过程,而不是一个固定的全集。
-
为每幅图片编码,命名
假设从屏幕截取一个图片并存储 , 为了方便确认每幅图片对应的测试环境,我们按一定的规则自动为图片命名,例如 temp_1024x768_winxp_gb2312_ie6.png, temp_1024x768_redhat4_utf8_ff2.png 等。我们也可以采用编码的方式缩短文件的名称。
-
自动生成期望图片集合
手动建立期望结果集合是个费时且不合理的工作,期望结果集需要自动建立。
在测试过程中,当执行图片验证点时,我们将根据上面命名规则对得到的图片命名,譬如 temp_1024x768_winxp_gb2312_ie6.png。接着在图片集合中去寻找和该命名具有相同名称的图片。如果没有找到,则将当前图片保存到期望图片集合中作为基准,并在测试结果报告中标记错误信息,以方便查询和更正。也就是说当执行图片验证时如果从结果集中找不到期望的图片,将当前的截图作为期望的图片,如果后来经验证该截图不正确则可手动更正。
通过自动建立结果集合这个方法,我们提高工作效率。另外,依据这种方式建立的是我们实际需要的结果集,是全集(包含 n*m*x*y 中图片)的一个子集,方便我们对结果图片集合的维护。
结果集的维护
结果集的维护可分为合并和扩展 2 种情况。
有时候,结果集合中会出现完全一样的图片,这时,我们将这两个图片进行合并。例如对于某一图片,在 IE6 中得到的期望截图 temp_1024x768_gb2312_ie6.png 和 FireFox2 中的期望 temp_1024x768_gb2312_ff2.png 完全一致,我们就可以合并这两幅截图,生成新的截图 temp_1024x768_gb2312.png。
当在 IE6 中执行并进行图片对比时,将先搜索带有浏览器信息的基准图片并对比;如果搜索失败,将搜索不带浏览器信息的基准并对比;反之,说明结果集里没有相应的基准图片。
在测试的初期,我们可能只覆盖某一平台。假如只覆盖 windows 平台,则结果集中的基准图片的命名可能不带平台信息,例如 temp_1024x768_gb2312_ie6.png。
但是在测试的后期,我们增加了支持的平台,譬如说 Linux,此时我们的结果集中的图片就需要添加平台信息加以区分。此时,我们需要使用加入了平台信息的算法使得自动生成的图片名称中带有平台相关的信息。
例如,在 windows 平台上执行时,我们在结果集中找不到带有相应平台信息的基准图片,但找到了不带平台信息的基准,则将该基准重新命名为带有相应平台信息的基准。当在 linux 平台上执行时,在结果集中找不到带有相应平台信息的基准图片且找不到不带平台信息的基准,则将当前截图作为基准放入结果集合中。
基于结果集图片验证的应用
上面从理论上介绍了如何基于结果集的方法实现图片的比对并介绍了如何处理图片比对的不可重现性和不可移植性。下面我们将结合一个具体例子介绍如何实现基于结果集的自动化图片对比。
假设我们要测试一个基于 Elcipse3.2 开发的产品。该产品支持 winxp 操作系统,使用英文编码和 eclipse 应用程序显示,适用于不同的分辨率。
具体步骤:
分析产品覆盖的特性,对图片进行命名
由于测试的产品当前覆盖单一的操作系统,单一的字符编码,单一的程序显示 ( 基于 Eclipse) 和不同的分辨率,因此我们的图片可以命名为:文件名称 _widthxheight.png。如何我们测试的产品以后要覆盖不同的操作系统、不同的字符编码 (charset) 和应用程序显示 (eclipse.version),我们的图片可以根据需要命名为:
文件名称 _widthxheight_os_charset_eclipse.version.png。
清单 1. 产生图片名称
public static String generateGolenImageName(String name, int width, int height) {
String result = "";
if (name.endsWith(PNG_TYPE)) {
// 如果覆盖不同的操作系统(os),不同的字符编码 (charset),和应用程序显示
result = name.substring(0,name.indexOf(PNG_TYPE)) + "_" + width+ "x" + height + PNG_TYPE;
} else {
result = name + "_" + width + "x" + height + "_" + PNG_TYPE;
}
return result;
}
|
建立自己的图片编码器
为了使我们的图片比对可以跨机器和操作系统,我们必须使用统一的图片编码器来存储屏幕的截图。我们可以自己创建 PNG 图片编码器,也可以使用第三方的 PNG 编码器。我们自定义了一个 PNG 编码器类:com.ibm.datatools.util.PngEncoder(代码请见附件)。
图片对比和期望图片集的自动建立
在测试的过程中,我们得到了图 1 所示的屏幕截图(image1.png)。在获取了截图的宽度和高度并计算出期望图片的名称后,而后在期望集合中搜索这个图片,如果图片存在就进行比对(XOR 运算),将比对结果存入新的图片,如果图片不存在则将其存入期望集合中。
图 1. 测试过程中获得的截图(查看大图)
清单 2. 获取基准图片
public static String getGoldenImageName(String name, int width, int height) {
String result = "";
if (name.endsWith(PNG_TYPE)) {
result = name.substring(0, name.indexOf(PNG_TYPE)) + "_"
+ width + "x" + height + PNG_TYPE;
File file = new File(result);
if (!file.exists()) {
result = name.substring(0, name.indexOf(PNG_TYPE)) + "_"
+ width + "x" + height + PNG_TYPE;
file = new File(result);
if (!file.exists()) {
result = name;
}
}
} else {
result = name + "_" + width + "x" + height + PNG_TYPE;
File file = new File(result);
if (!file.exists()) {
result = name + "_" + width + "x" + height + PNG_TYPE;
file = new File(result);
if (!file.exists()) {
result = name;
}
}
}
return result;
}
|
清单 3. 对比当前截图与基准图片
public static boolean compareImage(String productImage, String goldenImage) {
boolean result = false;
try {
File pImage = new File(productImage);
BufferedImage bio = ImageIO.read(pImage);
int height = bio.getHeight();
int width = bio.getWidth();
int[] rgb = new int[height * width];
bio.getRGB(0, 0, width, height, rgb, 0, width);
// bio.get
String goldenName = getGoldenImageName(goldenImage, width, height);
File goldenFile = new File(goldenName);
if (goldenFile.exists()) {
BufferedImage bit = ImageIO.read(new File(goldenName));
int height1 = bit.getHeight();
int width1 = bit.getWidth();
int[] rgb1 = new int[height1 * width1];
bit.getRGB(0, 0, width1, height1, rgb1, 0, width1);
if (Arrays.equals(rgb, rgb1)) {
RationalTestScript.logInfo("this two image is equal.<br/>");
RationalTestScript.logInfo("Output image:" + productImage
+ "<br/>" + "<IMG src=\"" + productImage
+ "\" height=" + height + " width=" + width
+ " align=middle></IMG>"
+ ".<br/>GoldImage:" + goldenName + "<br/>"
+ "<IMG src=\"" + goldenName + "\" height="
+ height + " width=" + width
+ " align=middle></IMG>");
result = true;
} else {
ImageXor imageX = new ImageXor(width, width1);
int[] des = imageX.doXor(rgb, rgb1, true, width, height, 0, 1);
System.out.println(bio.getType());
System.out.println(bit.getType());
BufferedImage bdest = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
bdest.setRGB(0, 0, width, height, des, 0, width);
PngUtil.write(bdest, productImage + "_diff_" + PNG_TYPE);
RationalTestScript.logError("Compare image failed.<br/>");
RationalTestScript.logInfo("Output image:" + productImage
+ "<br/>" + "<IMG src=\"" + productImage
+ "\" height=" + height + " width=" + width
+ " align=middle></IMG>"
+ ".<br/>GoldImage:" + goldenName + "<br/>"
+ "<IMG src=\"" + goldenName + "\" height="
+ height + " width=" + width
+ " align=middle></IMG>"
+ ".<br/>The differ image is below." + productImage
+ "_diff_" + PNG_TYPE + "<br/><IMG src=\""
+ productImage + "_diff_" + PNG_TYPE + "\" height="
+ height + " width=" + width
+ " align=middle></IMG>");
}
} else {
RationalTestScript.logError("Can't find equal size golden image<br/>");
goldenImage = generateGolenImageName(goldenImage, width, height);
BufferedInputStream bif = new BufferedInputStream(new FileInputStream(pImage));
BufferedOutputStream bof = new BufferedOutputStream(new FileOutputStream(goldenImage));
byte[] buffer = new byte[4 * 1024];
int length = 0;
while ((length = bif.read(buffer, 0, 4 * 1024)) != -1) {
bof.write(buffer, 0, length);
}
bof.flush();
bof.close();
bif.close();
RationalTestScript.logError("automate generate golden image:"
+ goldenImage + "<br/>" + "<IMG src=\"" + goldenImage
+ "\" height=" + height + " width=" + width
+ " align=middle></IMG>");
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
|
执行结果
当在期望集合中找不到相同大小的期望图片时,测试报告中将提示找不到期望相同大小的图片的错误,并自动将现有的截图 image1.png 重新命名以带有分辨率或其他信息,如 image1_1014x655.png,并加入期望集合中,这样在下次运行时就能找到这张截图。
如果进行验证时在期望集合中能找到可比对的图片,就会比对 image1.png 和预期图片,譬如 image1_1014x655.png。如果比对失败,比对结果将被放入一个临时的图片中 image1.png_diff.png。如果没有产生 diff 文件就说明这两幅图片是完全相等的(每一个像素点都相等)。
-
第一次运行时,产生了期望结果(image1_1014x655.png)
图 2. 期望结果的生成(查看大图)
图 3. 当前截图(查看大图)
-
与期望基准 image1_1014x655.png 比对,产生临时文件 image1.png_diff.png,说明比对失败
图 4. 对比结果(查看大图)
总结
对于图片的比较,在需要比对的图片较少时,我们可以用肉眼去分辨。但是,当需要对很多图片进行比较,特别是需要在不同的平台和图片浏览工具(比如 IE6/7,FireFox1.x/2.x)执行比对时,手工的方式就变得繁杂和费时。
基于结果集的图片验证方法能够很好的解决分辨率大小、操作系统、字符集、显示图片的软件等因素对图片比对的影响,通过自动化的方式简化图片比对这一工作。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 示例代码 | com.ibm.datatools.util.PngEncoder.java | 21 KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
作者简介  | |  | 赵森是 IBM 中国开发中心的软件工程师,主要从事基于 Rational Functional Tester 自动化测试框架的开发。 |
 | |  | 梁佩佩是 IBM 中国软件开发中心的软件工程师,主要负责 DB2 Warehouse 中数据挖掘工具和文本分析工具的测试。 |
对本文的评价
|