Sikuli-基于图像识别的自动化测试框架
相关术语
缩写 | 全称 | 描述 |
---|---|---|
Accessibility | 辅助功能 | 通过应用提供的Accessibility特性可以定位到相应的元素 |
IDE | Integrated Development Environment | 集成开发工具 |
sikulixIDE | sikulixIDE | Sikuli自带的集成开发工具 |
问题
在UOS操作系统桌面应用的GUI自动化测试中,通常的解决方案是对桌面应用添加Accessibility,然后通过Dogtail或LDTP这类工具去获取应用的元素,从而可以对元素进行定位和操控。
UOS操作系统桌面应用在开发的时候,并没有添加Accessibility,那么我们现在要通过这种方法去做自动化测试,只能让开发人员重新去对各个应用的控件去添加Accessibility,以便于我们开展自动化测试。
那么,桌面应用自动化测试除了通过获取元素的属性来定位和操作以外,有没有不依赖于Accessibility的自动化测试方案呢?
现状
目前,我们针对Linux操作系统桌面应用进行自动化测试时,使用Accessibility来定位和操作应用元素,Accessibility(辅助功能)在很多操作系统中都存在,比如windows、IOS、Android。
这个实现功能实现的初衷是让残疾人士也可以使用操作系统,就像freedesktop官网关于Accessibility是这样描述的:"世界人口的15%生活在某种形式的残疾中,辅助功能对很多用户很重要,没有它,他们就是不能使用他们的电脑。"
Accessibility有几个重要原则要牢记在心
- 我们希望使现有软件可访问,并避免专用软件
- 我们需要同步:可访问性只是输入和输出的另一种方式
- 它应该很容易获得,随时可以启用
因此,基于Accessibility的这种可访问性的特性,就可以辅助我们做自动化测试。
但具体到应用,就要看应用在编码的时候是否加了Accessibility的属性,如果没有添加Accessibility,则无法被识别到,后期去添加需要耗费更多的人力成本和时间成本。
技术方案
Sikuli是麻省理工学院的一个开源项目,一种新颖的图形脚本语言,它是一个基于图像识别的GUI自动化测试框架,底层是基于opencv实现对图像的识别,使用者只需要会最简单的编程技能,就能轻松的使用它。
Sikuli 在墨西哥维乔印第安人的语言里是上帝之眼的意思,所以被称为“上帝之眼”。与通常所见的自动化测试框架不同,Sikuli不需要应用添加任何的属性,仅通过图像就可以对元素进行定位和操作,所谓“所见即所得”,只要眼睛能够看到的,sikuli就能够识别到,无论是web端、App端、桌面端,都可以轻松实现跨平台的自动化测试,具有很强的兼容性。
整体设计
sikuli分为三个模块:
- sikulixIDE
sikulixIDE是在.sikuli
结尾的目录中编写sikuli的脚本,以及编辑png格式的图片,然后在java环境中,使用Jython执行sikuli的脚本。
- 以
.sikuli
结尾的目录
目录中保存有sikuli的脚本,以及png格式的图片,其中脚本是py文件,图片可以有两种形式截取,一种是通过sikulixIDE工具提供的截图功能直接截图,截图之后默认保存的名称是随机数字,当然我们可以在文件管理器中将图片名称进行修改,另一种是通过三方工具截取png格式的图片,命名可以自定义。
- sikuli脚本
sikuli脚本为.sikuli
目录下的py文件,一个目录下只有一个py文件,一般编写脚本的时候是在IDE工具里面进行编写,在熟悉sikuli的语法之后,也可以采用其他的编码工具进行编写,只要主要图片名称与代码的关联关系即可。
sikuli在执行的时候流程如下:
- 通过sikulixIDE,可以建立sikuli脚本,其中包括Python源代码以及所需要的截图。
- SikulixIDE执行脚本时,通过Python解析器和java库的桥梁,核心部分解析是通过java库实现的。
- 调用opencv在对截取的图片进行比对搜索。
- 当搜索到对应的图片后,会调用java.awt.Robot控制鼠标和键盘事件,从而实现相应的操作。
关键技术
环境
在UOS系统中搭建以下环境:
Java环境
由于sikuli调用的opencv的Java API,所以依赖Java环境
sudo apt-get install openjdk-8-jre
安装opencv
sudo apt-get install libopencv3.2-java
sudo ln -s /usr/lib/jni/libopencv_java320.so /usr/lib/libopencv_java.so
安装tesseract
sudo apt-get install tesseract-ocr
sudo apt-get install libtesseract-dev
sudo apt-get install libleptonica-dev
下载sikulixIDE
wget https://launchpadlibrarian.net/469010975/sikulixide-2.0.4.jar
在Sikuli的IDE工具中,可以对脚本进行开发。
下载jython
wget https://repo1.maven.org/maven2/org/python/jython-standalone/2.7.1/jython-standalone-2.7.1.jar
jython实现了使用Python语言调用java的功能。
运行IDE
将sikulixIDE和jython放在统一目录下,然后切换到这个目录下,
在终端输入:java -jar sikulixide-2.0.4.jar
即可启动sikuli的IDE工具,然后就可以在IDE中进行脚本编写
点击
Click()
将光标定位到括号内,使用IDE工具提供的截图功能,截取我们想要点击的图标,图片就会自动显示在括号内
如果是使用其他工具截取的图片,只需要括号内直接输入图片的路径即可(格式为png)。
比如:
Click(computer.png) # 运行之后鼠标会去点击“我的电脑”图标
双击
doubleClick()
使用方法和Click()类似,使用IDE工具直接截图,或输入图片的路径。(以下没做说明的,都是采用这种方法)
比如:
doubleClick(computer.png) # 运行之后鼠标回去双击“我的电脑”图标
右键点击
rigthClick()
拖拽
dragDrop(png1,png2) # from png1 to png2
括号内写两个图片的路径,表示从png1拖拽到png2的位置。
检查是否存在
exists(png)
这个方法通常用于断言
# 判断图片是否存在
if exists(png1):
print("png1存在")
else:
print(png1不存在)
睡眠
sleep() # sleep(1)
括号内写睡眠的时间,单位是秒,这个方法类似于python里面,time.sleep()表示脚本运行时,暂定几秒的时间。
输入内容
type("text")
括号内写要输入的文本内容。
键盘
控制字符
type(Key.ENTER) # 表示按回车键
括号内是Key加上大写的控制字符,常见的ENTER, TAB, ESC, BACKSPACE, DELETE, INSERT等等
快捷键
type(“ c”,Key.CTRL) # 表示Ctrl + c
括号内是控制字符的组合按键,常见的ALT, CMD, CTRL, SHIFT, WIN等等,其中三个按键的情况要特殊说明下,
type(Key.ESC,Key.CTRL + Key.SHIFT) # 表示Ctrl + Shift +ESC
运算符
运算符与python里面使用方法相同,+, - ,*, /, <, >, =,and,or,not等等,这里不做展开说明。
实验验证
以相册为例
编写相册的用例脚本:
1.封装基础方法
定义写用例之前要用到的方法
import time
import os
import getpass
times = time.strftime("%Y_%m_%d %H:%M:%S")
username = getpass.getuser()
#===================================================================================
# 定义用例操作步骤所要用到的方法
#===================================================================================
# 定义写日志的方法
def report(txt):
print(txt)
file = "./report/album_report_%s.report" % times
with open(file,"a") as f:
f.write(txt + "\n")
# 如果存在图标,打印log_t,如果不存在图标,打印log_f,(通常用于断言)
def if_exists(pic, log_t, log_f):
sleep(0.3)
log_f = log_f + "====================Fail"""
# log_f后面加的Fail是为了在日志文件中能够重点体现出来,一眼就可以看到哪些用例失败
if exists(pic,3):
report(log_t)
else:
report(log_f)
# 如果不存在图标,打印log_t,如果不存在图标,打印log_f
def if_not_exists(pic, log_t, log_f):
sleep(0.3)
if not exists(pic):
report(log_t)
else:
report(log_f)
# 如果存在图标,则点击图标,如果不存在,则打印报错信息。
def find_and_click(pic, log_f):
sleep(0.3)
log_f = log_f + "====================Fail"
if exists(pic,3):
click(pic)
else:
report(log_f)
# 双击图标
def find_and_double_click(pic, log_f):
sleep(0.3)
log_f = log_f + "====================Fail"
if exists(pic,3):
doubleClick(pic)
else:
report(log_f)
# 右键单击图标
def find_and_right_click(pic, log_f):
sleep(0.3)
log_f = log_f + "====================Fail"
if exists(pic,3):
rightClick(pic)
else:
report(log_f)
# 在桌面空白处右键点击
def right_click_on_desktop(jsj = "1596177723547.png"):
sleep(0.3)
if exists(jsj):
btn = find(jsj).right(800)
rightClick(btn)
sleep(0.3)
else:
report("计算机图标不存在!")
# 关闭窗口
def close_window():
#find_and_click(Pattern("1596524278245.png").targetOffset(89, -3), "窗口关闭失败")
if exists("1596524278245.png"):
click(Pattern("1596524278245.png").targetOffset(89, -3))
# 关闭所有窗口
def close_all_window():
n = 0
while n < 3:
if exists("1596524278245.png"):
close_window()
n = n + 1
else:
report("环境清理:关闭所有窗口")
break
# 所有窗口最小化
def min_window():
#if_exists_and_click(Pattern("1596524278245.png").targetOffset(-12,0),"最小化窗口失败")
if exists("1596524278245.png"):
click(Pattern("1596524278245.png").targetOffset(-12,0))
# 最小化所有窗口
def min_all_window():
while True:
if exists("1596524278245.png"):
min_window()
else:
report("环境清理:最小化所有窗口")
break
# 命令行执行的方法
def cmd(doit):
os.system(doit)
# 删除桌面文件
def delete_desktop_file(format):
cmd("rm /home/%s/Desktop/*.%s" % (username,format))
# 杀进程
def kill_process(process):
cmd("ps -ef | grep %s | grep -v grep | cut -c 9-15 | xargs kill -9" % process)
# 从任务栏打开相册
def open_album():
find_and_click(Pattern("1596610577797.png").targetOffset(-2,5),"相册应用图标未找到")
#===================================================================================
2.编写用例脚本
用例的脚本实际上是基于之前封装好的方法,传入相应的图片文件,以及断言的文本即可。
def test_album_1():
report("用例001:外部调用相册应用")
find_and_double_click("1596609261881.png","主目录没找到")
sleep(1)
find_and_click("1596609375609.png","图片目录未找到")
sleep(1)
if exists("1596609484192.png"):
find_and_double_click("1596609484192.png","Wallpapers图标没找到")
sleep(1)
elif exists("1597228353694.png"):
find_and_double_click(Pattern("1597228353694.png").targetOffset(48,58),"Wallpapers图标没找到")
sleep(1)
find_and_right_click("1596609522418.png","图片未找到")
find_and_click("dakaifangshi.png","打开方式选项未找到")
find_and_click("yixiangce.png","相册选项未找到")
# 断言
if_exists("1596609775470.png","外部调用相册成功","外部调用相册失败")
# 关闭打开的窗口
kill_process("deepin-album")
close_all_window()
可以看到,用例脚本里面大多都是传入的图片名称,图片名称为数字的,例如:1596609261881.png,是使用sikulixIDE工具提供的截图功能直接截图的,默认是保存到当前目录下,而使用单词命名的是通过三方截图工具截取的,例如:dakaifangshi.png,我们将图片放到当前目录下就可以被识别到。
如果我们在截图图片的时候弄乱了,当前目录下存在脚本里面没有被用到的图片,Sikuli的IDE工具会自动检测,将没用的图片删除。
3.执行的脚本
由于sikulixIDE工具没有提供组织用例的功能,在一个py文件中,我们定义了多个用例,但是在执行的时候,可能只需要执行部分的用例,那么我们就需要编写组织用例的方法。
doit = range(1,2)
for i in doit:
i = str(i)
eval("test_album_%s()" % i)
这里面用到python里面的eval函数,就是将字符串转换成脚本来执行,我们组装成一个用例的方法名,即可实现用例的执行,在for循环中,我们用doit这个列表在组装要测试的用例,这里我用的是range函数,也可以在一个列表中定义要执行的用例序号。
小结
优点:
- Sikuli在UOS操作系统的环境搭建比较简单,所有元素控件均以图片的形式进行保存,不依赖于应用的属性。
- 在sikuliIDE中使用简单的编程语法,就能实现对自动化测试脚本进行编写,语法简单易懂,即使时不懂编程的人,也能快速上手,编写自己的自动化测试用例。
- IDE中提供了截图的功能,截图后可以直接在代码中显示。
- 可以设置图片的相对位置,方便我们定位相对目标位置的任意位置。
- 不对应用的安全性造成应用,可以实现测试环境与开发环境的隔离。
缺点:
- 所有元素控件均以图片的形式进行保存,用例较多时,需要保存大量的图片,项目会比较臃肿。
- 后期维护性比较差,需要修改图片,重新截图等,比较容易乱,通过IDE自带的截图工具截取的图片,名称为随机数,从名称上不容易识别。
- IDE没有自动生成测试报告的能力,需要在代码中自己实现。
- IDE报错模糊,不能很好的定位代码问题,如果要封装一些方法,需要特别注意,这点来讲就需要有较强的编码能力。
- IDE没有补全代码的功能,易用性差。
- 批量执行方便,IDE中不提供批量执行的功能,在命令行中执行时也不能进行测试用例的组织。
- 无法和其他框架配合使用,也不能导入三方模块。
综上,sikuli是基于图像识别的自动化测试方案,脚本语法简单,可以简单快速的编写测试用例,即使是初级工程师也可以轻松的使用并编写测试脚本,可以用于UOS桌面应用的自动化测试。
参考文档
sikuli官方文档:https://sikulix-2014.readthedocs.io/en/latest/region.html
sikuli使用文档:
https://sikulix-2014.readthedocs.io/en/latest/region.html#Region.exists
https://sikulix-2014.readthedocs.io/en/latest/region.html#lowlevelmouseandkeyboardactions
本文来自博客园,作者:mikigo,转载请注明原文链接:https://www.cnblogs.com/mikigo/p/14301160.html