APP自动化测试之UiAutomator2.0颜色验证方案
移动端APP的Android自动化测试中会有不少背景色、透明度等测试验证,但各种测试框架都没有提供获取对象背景色等API,那么面对这样的测试需求,我们该如何进行呢?
一、方案思路
直接在测试框架上面做文章貌似不可行了,那么我们就要转向Android系统本身了,要验证背景色,首先自然得获取颜色值,UIAutomator2.0等框架没有提供对应API,那么我们还可以通过截图的方式获取元素图片,最直接的方式就可以通过像素点的方式获取颜色值,这样获得的颜色值是基于RGB数值的……这好像有点抽象,还是先了解这些颜色空间概念:
1、RGB颜色模型
在图像处理中,最常用的颜色空间是RGB模型,常用于颜色显示和图像处理,它是三维坐标的模型形式,如下图表示一个三维坐标空间:
其他太复杂的原理我在这里就不深究(毕竟不是做图像开发,再深入的我也不懂了),我们只需要了解RGB模型的3维坐标系(r,g,b)分别代表的是红绿蓝三基色的值:
red红色值(0~255)
green绿色值(0~255)
blue蓝色值(0~255)
2、HSV颜色模型
HSV模型是一个倒锥形模型,如下图所示:
这个模型是按色彩、深浅、明暗(透明度)来描述的,其中:
H:表示色彩,即色度值
S:表示深浅, 即通常说的透明度指标,S = 0时,只有灰度
V:表示明暗,说明色彩的明亮程度
3、RGB与HSV之间的关系
这段其实我不想贴,但是有些学霸还是看得明白的,那就当个搬运工,给学霸们看吧,不想了解原理的童鞋直接跳过关系这块。直观的理解,把RGB三维坐标的中轴线立起来,并扁化,就能形成HSV的锥形模型了。但V与强度无直接关系,因为它只选取了RGB的一个最大分量。而RGB则能反映光照强度(或灰度)的变化。
由RGB到HSV的转换:
OK,言归正传,了解了上面这些颜色模型之后,可以清楚的发现使用HSV可以较大程度的排除掉光线或者分辨率等深浅明暗等外在因素导致的色差,直接去颜色值H可以较准确的进行颜色对比。然而我们通过取截图的像素点颜色得到的RGB三维数组,那么要想实现HSV模型的H值对比,还得进行转换,转换的计算规则就是上面3中的RGB与HSV转换计算(网上大多是C++版本的转换,我下面给出Java版本的实现)。
二、实现代码
下面列举出核心代码并说明:
RGB数组转换为HSV数组,一般只用HSV数组的色度值H和饱和度S
public static double[] rgbToHsv(int r, int g, int b){
double h, s, v;
double min, max, delta;
min = Math.min(Math.min(r, g), b);
max = Math.max(Math.max(r, g), b);
// V 亮度
v = max;
delta = max - min;
// S 饱和度
if( max != 0 ) {
s = delta / max;
}else {
s = 0;
h = -1;
return new double[]{h,s,v};
}
// H 色度
if( r == max ){
h = (g - b) / delta; // between yellow & magenta
}else if( g == max ) {
h = 2 + (b - r) / delta; // between cyan & yellow
}else {
h = 4 + (r - g) / delta; // between magenta & cyan
}
h *= 60; // degrees
if( h < 0 ) {
h += 360;
}
return new double[]{h,s,v};
}
获取对象区域的指定像素点RGB模型数组值
/**
* 图片区域中指定坐标的rgb
* @param rect
* @param filePath
* @param pixelX
* @param pixelY
* @return
*/
public static int[] getColorRgb(Rect rect, String filePath,
int pixelX, int pixelY) {
BitmapFactory.Options bfOptions = new BitmapFactory.Options();
bfOptions.inDither = false;
bfOptions.inTempStorage = new byte[12 * 1024];
bfOptions.inJustDecodeBounds = true;
Bitmap m = BitmapFactory.decodeFile(filePath);
m = m.createBitmap(m, rect.left, rect.top,
rect.width(), rect.height());//获取区域
int color = m.getPixel(pixelX, pixelY);
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
m.recycle();
int [] rgb = {r,g,b};
return rgb;
}
验证颜色方法,颜色对比验证方法的验证逻辑是首先比对RGB,RGB不一致再对比HSV的色度值H:
public static boolean assertColor(int[] actualRgb, int[] expectedRgb, String msg){
if(actualRgb == null || expectedRgb == null || actualRgb.length != 3
|| expectedRgb.length != 3){
BaseCase.log.e("RGB数组为空或长度不为3", isPrintLog);
return false;
}
String actual = TestUtil.rgbToString(actualRgb);
String expected = TestUtil.rgbToString(expectedRgb);
try {
org.junit.Assert.assertEquals(actual, expected);
BaseCase.log.i("实际"+ msg +"的RGB值:\""+actual+"\" ,
跟期望"+ msg +"的RGB值:\""+expected+"\" 相匹配", isPrintLog);
return true;
} catch (Error e) {
BaseCase.log.i("实际"+ msg +"的RGB值:\""+actual+"\" ,
跟期望"+ msg +"的RGB值:\""+expected+"\" 不匹配,下面对比HSV的色度值:");
double[] actualHsv = TestUtil.rgbToHsv(actualRgb[0],actualRgb[1],actualRgb[2]);
double[] expectedHsv = TestUtil.rgbToHsv(expectedRgb[0],expectedRgb[1],expectedRgb[2]);
double hRate = Math.abs(actualHsv[0]-expectedHsv[0])/actualHsv[0];
double sRate = Math.abs(actualHsv[1]-expectedHsv[1])/actualHsv[1];
if(hRate < 0.05){
BaseCase.log.i("实际"+ msg +"的HSV色度值:\""+actualHsv[0]+"\" ,
跟期望"+ msg +"的色度值:\""+expectedHsv[0]+"\" 相匹配", isPrintLog);
return true;
}
if(isPrintLog){
BaseCase.log.e("实际"+ msg +"的HSV色度值:\""+actualHsv[0]+"\" ,
跟期望"+ msg +"的色度值:\""+expectedHsv[0]+"\" 不匹配", isPrintLog);
}
return false;
}
}
验证透明度方法,透明度验证使用HSV模型的S值来对比:
public static boolean assertAlpha(int[] actualRgb, double expectedAlpha, String msg){
if(actualRgb == null || actualRgb.length != 3){
BaseCase.log.e("RGB数组为空或长度不为3",isPrint);
return false;
}
double hsv_s = rgbToHsv(actualRgb[0],actualRgb[1],actualRgb[2])[1];
hsv_s = (double)Math.round(hsv_s*1000)/1000.0;
double actualAlpha = Double.parseDouble(new DecimalFormat("0.0").format(hsv_s));
try {
org.junit.Assert.assertEquals(actualAlpha, expectedAlpha);
BaseCase.log.i("实际"+ msg +"的透明度:\""+actualAlpha+"\" ,跟期望"+ msg +"的透明度:\""+expectedAlpha+"\" 相匹配",isPrint);
return true;
} catch (Error e) {
double sRate = hsv_s > 0.0 ? Math.abs(hsv_s-expectedAlpha)/hsv_s : 1;
if(hsv_s == expectedAlpha){
BaseCase.log.i("实际"+ msg +"的透明度:\""+actualAlpha+"\" ,
跟期望"+ msg +"的透明度:\""+expectedAlpha+"\" 相匹配",isPrint);
return true;
} else if(sRate < 0.1){
BaseCase.log.i("实际"+ msg +"的透明度:\""+actualAlpha+"\" ,
跟期望"+ msg +"的透明度:\""+expectedAlpha+"\" 在误差范围内相匹配",isPrint);
return true;
}
if(isPrint){
BaseCase.log.e("实际"+ msg +"的透明度:\""+actualAlpha+"\" ,
跟期望"+ msg +"的透明度:\""+expectedAlpha+"\" 不匹配",isPrint, msg + "不匹配");
}
return false;
}
}
测试用例方法中验证颜色示例:
int[] bgColorRgb = TestUtil.getRandomColorRgb();
setText(getImageBgColor(),TestUtil.rgbToString(bgColorRgb));assertColor(getColor(adPage.getAdImage(),0,0),bgColorRgb,"大图背景色");
三、总结
用这种方式验证颜色感觉妥妥的,但是也有可能会出现不准的情况,因为是基于像素点取色,依赖于像素点的坐标,然而对于不是纯色的颜色或者图片包含其他杂色,这时候使用像素点就不能随便取了,你可以拿一个准确的点,或者可以不用考虑效率的话,采用遍历全区域取色,再计算占比,将占比最大的RGB值返回,这样准确率肯定高了,但是效率就慢了,特别是对比尺寸较大的图,图像对比操作本来就效率较低且很耗系统资源,所以一般做图像处理的都用C++,当然市面上好像也有一些比较好的图像对比算法和框架,想进一步研究就得靠自己去看文档和研究了。
测试开发栈
软件测试开发合并必将是趋势,不懂开发的测试、不懂测试的开发都将可能被逐渐替代,因此前瞻的技术储备和知识积累是我们以后在职场和行业脱颖而出的法宝,期望我们的经验和技术分享能让你每天都成长和进步,早日成为测试开发栈上的技术大牛~~
长按二维码/微信扫描关注
互联网测试开发一站式全栈分享平台