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在执行的时候流程如下:

  1. 通过sikulixIDE,可以建立sikuli脚本,其中包括Python源代码以及所需要的截图。
  2. SikulixIDE执行脚本时,通过Python解析器和java库的桥梁,核心部分解析是通过java库实现的。
  3. 调用opencv在对截取的图片进行比对搜索。
  4. 当搜索到对应的图片后,会调用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

posted @ 2021-01-20 09:18  mikigo  阅读(5769)  评论(3编辑  收藏  举报