zoukankan      html  css  js  c++  java
  • 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 + "
    ")
            
    # 如果存在图标,打印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

    没伞的孩子,就要学会在雨中奔跑!
  • 相关阅读:
    java高级程序设计(第十周)
    java高级程序设计(第五周)
    java高级程序设计(第四周)
    期末设计(第十四周)
    期末设计(第十三周)
    期末设计(计划进度表)
    Java学习笔记(六)
    Java学习笔记(六)
    Java学习笔记(五)
    Java学习笔记(四)
  • 原文地址:https://www.cnblogs.com/mikigo/p/14301160.html
Copyright © 2011-2022 走看看