基于python+appium+yaml安卓UI自动化测试分享

简介: 结构介绍之前分享过一篇安卓UI测试,但是没有实现数据与代码分离,后期维护成本较高,所以最近抽空优化了一下。不想看文章得可以直接去Github,欢迎拍砖大致结构如下:结构.

结构介绍

之前分享过一篇安卓UI测试,但是没有实现数据与代码分离,后期维护成本较高,所以最近抽空优化了一下。
不想看文章得可以直接去Github,欢迎拍砖
大致结构如下:

结构.png

  • testyaml管理用例,实现数据与代码分离,一个模块一个文件夹

  • public 存放公共文件,如读取配置文件、启动appium服务、读取Yaml文件、定义日志格式等

  • page 存放最小测试用例集,一个模块一个文件夹

  • results 存放测试报告及失败截图

    report.png
  • logs 存放日志

    logs.png
    logdetail.png
  • testcase 存放测试用例
  • runtest.py 运行所有测试用例

yaml格式介绍

首先看下yaml文件的格式,之前也写过一点关于yaml语法学习的文章
testcase部分是重点,其中:

  • element_info:定位元素信息

  • find_type:属性,id、xpath、text、ids

  • operate_type: click、sendkeys、back、swipe_up 为back就是返回,暂时就四种

    上面三个必填,operate_type必填!!!!!!

  • send_content:send_keys 时用到

  • index:ids时用到

  • times: 返回次数或者上滑次数

testinfo:
    - id: cm001
      title: 新增终端门店
      execute: 1
testcase:
    -
      element_info: 客户
      find_type: text
      operate_type: click
    -
      element_info: com.fiberhome.waiqin365.client:id/cm_topbar_tv_right
      find_type: id
      operate_type: click
    -
      element_info: com.fiberhome.waiqin365.client:id/custview_id_singletv_inputtext
      find_type: ids
      operate_type: send_keys
      send_content: auto0205
      index: 0
    -
      element_info:
      find_type:
      operate_type: swipe_up
      times: 1
    -
      element_info: 提交
      find_type: text
      operate_type: click
    -
      element_info:
      find_type:
      operate_type: back
      times: 1

代码部分

公共部分

个人觉得核心的就是公共部分,相当于建房子,公共部分搞好了,后面仅仅是调用即可,建房子把架子搭好,后面就添砖加瓦吧。

读取配置文件readconfig.py
设置日志格式logs.py
获取设备GetDevices.py
这几个通用的就不做介绍了

  • 读取yaml文件 GetYaml.py
    主要用来读取yaml文件
#coding=utf-8
#author='Shichao-Dong'

import sys
reload(sys)
sys.setdefaultencoding('utf8')
import yaml
import codecs

class getyaml:
    def __init__(self,path):
        self.path = path

    def getYaml(self):
        '''
        读取yaml文件
        :param path: 文件路径
        :return:
        '''
        try:
            f = open(self.path)
            data =yaml.load(f)
            f.close()
            return data
        except Exception:
            print(u"未找到yaml文件")

    def alldata(self):
        data =self.getYaml()
        return data

    def caselen(self):
        data = self.alldata()
        length = len(data['testcase'])
        return length

    def get_elementinfo(self,i):
        data = self.alldata()
        # print data['testcase'][i]['element_info']
        return data['testcase'][i]['element_info']

    def get_findtype(self,i):
        data = self.alldata()
        # print data['testcase'][i]['find_type']
        return data['testcase'][i]['find_type']

    def get_operate_type(self,i):
        data = self.alldata()
        # print data['testcase'][i]['operate_type']
        return data['testcase'][i]['operate_type']

    def get_index(self,i):
        data = self.alldata()
        if self.get_findtype(i)=='ids':
                    return data['testcase'][i]['index']
        else:
            pass

    def get_send_content(self,i):
        data = self.alldata()
        # print data['testcase'][i]['send_content']
        if self.get_operate_type(i) == 'send_keys':
            return data['testcase'][i]['send_content']
        else:
            pass

    def get_backtimes(self,i):
        data = self.alldata()
        if self.get_operate_type(i)=='back' or self.get_operate_type(i)=='swipe_up':
                    return data['testcase'][i]['times']
        else:
            pass

    def get_title(self):
        data = self.alldata()
        # print data['testinfo'][0]['title']
        return  data['testinfo'][0]['title']

  • 启动appium服务 StartAppiumServer.py
    主要是启动appium并返回端口port,这个port在下面的driver中需要
#coding=utf-8
#author='Shichao-Dong'

from logs import log
import random,time
import platform
import os
from GetDevices import devices

log = log()
dev = devices().get_deviceName()

class Sp:
    def __init__(self, device):
        self.device = device

    def __start_driver(self, aport, bpport):
        """
        :return:
        """
        if platform.system() == 'Windows':
            import subprocess
            subprocess.Popen("appium -p %s -bp %s -U %s" %
                             (aport, bpport, self.device), shell=True)

    def start_appium(self):
        """
        启动appium
        p:appium port
        bp:bootstrap port
        :return: 返回appium端口参数
        """
        aport = random.randint(4700, 4900)
        bpport = random.randint(4700, 4900)
        self.__start_driver(aport, bpport)

        log.info(
            'start appium :p %s bp %s device:%s' %
            (aport, bpport, self.device))
        time.sleep(10)
        return aport

    def main(self):
        """
        :return: 启动appium
        """
        return self.start_appium()

    def stop_appium(self):
        '''
        停止appium
        :return:
        '''
        if platform.system() == 'Windows':
            os.popen("taskkill /f /im node.exe")

if __name__ == '__main__':
    s = Sp(dev)
    s.main()
  • 获取driver GetDriver.py
    platformName、deviceName、appPackage、appActivity这些卸载配置文件config.ini文件中,可以直接通过readconfig.py文件读取获得。
    appium_port有StartAppiumServer.py文件返回
s = Sp(deviceName)
appium_port = s.main()

def mydriver():
    desired_caps = {
                'platformName':platformName,'deviceName':deviceName, 'platformVersion':platformVersion,
                'appPackage':appPackage,'appActivity':appActivity,
                'unicodeKeyboard':True,'resetKeyboard':True,'noReset':True
                }
    try:
        driver = webdriver.Remote('http://127.0.0.1:%s/wd/hub'%appium_port,desired_caps)
        time.sleep(4)
        log.info('获取driver成功')
        return driver
    except WebDriverException:
        print 'No driver'

if __name__ == "__main__":
    mydriver()
  • 重新封装find等命令,BaseOperate.py
    里面主要是一些上滑、返回、find等一些基础操作
#coding=utf-8
#author='Shichao-Dong'

from selenium.webdriver.support.ui import WebDriverWait
from logs import log
import os
import time

'''
一些基础操作:滑动、截图、点击页面元素等
'''

class BaseOperate:
    def __init__(self,driver):
        self.driver = driver

    def back(self):
        '''
        返回键
        :return:
        '''
        os.popen("adb shell input keyevent 4")

    def get_window_size(self):
        '''
        获取屏幕大小
        :return: windowsize
        '''
        global windowSize
        windowSize = self.driver.get_window_size()
        return windowSize

    def swipe_up(self):
        '''
        向上滑动
        :return:
        '''
        windowsSize = self.get_window_size()
        width = windowsSize.get("width")
        height = windowsSize.get("height")
        self.driver.swipe(width/2, height*3/4, width/2, height/4, 1000)

    def screenshot(self):
        now=time.strftime("%y%m%d-%H-%M-%S")
        PATH = lambda p: os.path.abspath(
            os.path.join(os.path.dirname(__file__), p)
        )
        screenshoot_path = PATH('../results/screenshoot/')
        self.driver.get_screenshot_as_file(screenshoot_path+now+'.png')

    def find_id(self,id):
        '''
        寻找元素
        :return:
        '''
        exsit = self.driver.find_element_by_id(id)
        if exsit :
            return True
        else:
            return False

    def find_name(self,name):
        '''
        判断页面是否存在某个元素
        :param name: text
        :return:
        '''
        findname = "//*[@text='%s']"%(name)
        exsit = self.driver.find_element_by_xpath(findname)
        if exsit :
            return True
        else:
            return False

    def get_name(self,name):
        '''
        定位页面text元素
        :param name:
        :return:
        '''
        # element = driver.find_element_by_name(name)
        # return element

        findname = "//*[@text='%s']"%(name)
        try:
            element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_xpath(findname))
            # element = self.driver.find_element_by_xpath(findname)
            self.driver.implicitly_wait(2)
            return element
        except:
            self.screenshot()
            log.error('未定位到元素:'+'%s')%(name)

    def get_id(self,id):
        '''
        定位页面resouce-id元素
        :param id:
        :return:
        '''
        try:
            element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_id(id))
            # element = self.driver.find_element_by_id(id)
            self.driver.implicitly_wait(2)
            return element
        except:
            self.screenshot()
            log.error('未定位到元素:'+'%s')%(id)

    def get_xpath(self,xpath):
        '''
        定位页面xpath元素
        :param id:
        :return:
        '''
        try:
            element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_xpath(xpath))
            # element = self.driver.find_element_by_xpath(xpath)
            self.driver.implicitly_wait(2)
            return element
        except:
            self.screenshot()
            log.error('未定位到元素:'+'%s')%(xpath)

    def get_ids(self,id):
        '''
        定位页面resouce-id元素组
        :param id:
        :return:列表
        '''
        try:
            # elements = self.driver.find_elements_by_id(id)
            elements = WebDriverWait(self.driver, 10).until(lambda x: x.find_elements_by_id(id))
            self.driver.implicitly_wait(2)
            return elements
        except:
            self.screenshot()
            log.error('未定位到元素:'+'%s')%(id)

    def page(self,name):
        '''
        返回至指定页面
        :return:
        '''
        i=0
        while i<10:
            i=i+1
            try:
                findname = "//*[@text='%s']"%(name)
                self.driver.find_element_by_xpath(findname)
                self.driver.implicitly_wait(2)
                break
            except :
                os.popen("adb shell input keyevent 4")
                try:
                    findname = "//*[@text='确定']"
                    self.driver.find_element_by_xpath(findname).click()
                    self.driver.implicitly_wait(2)
                except:
                    os.popen("adb shell input keyevent 4")
                try:
                    self.driver.find_element_by_xpath("//*[@text='工作台']")
                    self.driver.implicitly_wait(2)
                    break
                except:
                    os.popen("adb shell input keyevent 4")
  • Operate.py
    我认为最关键的一步了,后面没有page都是调用这个文件进行测试,主要是根据读取的yaml文件,然后进行if...else...判断,根据对应的operate_type分别进行对应的click、sendkeys等操作
#coding=utf-8
#author='Shichao-Dong'

from GetYaml import getyaml
from BaseOperate import BaseOperate

class Operate:
    def __init__(self,path,driver):
        self.path = path
        self.driver = driver
        self.yaml = getyaml(self.path)
        self.baseoperate=BaseOperate(driver)

    def check_operate_type(self):
        '''
        读取yaml信息并执行
        element_info:定位元素信息
        find_type:属性,id、xpath、text、ids
        operate_type: click、sendkeys、back、swipe_up 为back就是返回,暂时就三种

        上面三个必填,operate_type必填!!!!!!

        send_content:send_keys 时用到
        index:ids时用到
        times:
        :return:
        '''

        for i in range(self.yaml.caselen()):
            if self.yaml.get_operate_type(i) == 'click':
                if self.yaml.get_findtype(i) == 'text':
                    self.baseoperate.get_name(self.yaml.get_elementinfo(i)).click()
                elif self.yaml.get_findtype(i) == 'id':
                    self.baseoperate.get_id(self.yaml.get_elementinfo(i)).click()
                elif self.yaml.get_findtype(i) == 'xpath':
                    self.baseoperate.get_xpath(self.yaml.get_elementinfo(i)).click()
                elif self.yaml.get_findtype(i) == 'ids':
                    self.baseoperate.get_ids(self.yaml.get_elementinfo(i))[self.yaml.get_index(i)].click()

            elif self.yaml.get_operate_type(i) == 'send_keys':
                if self.yaml.get_findtype(i) == 'text':
                    self.baseoperate.get_name(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))
                elif self.yaml.get_findtype(i) == 'id':
                    self.baseoperate.get_id(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))
                elif self.yaml.get_findtype(i) == 'xpath':
                    self.baseoperate.get_xpath(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))
                elif self.yaml.get_findtype(i) == 'ids':
                    self.baseoperate.get_ids(self.yaml.get_elementinfo(i))[self.yaml.get_index(i)].send_keys(self.yaml.get_send_content(i))

            elif self.yaml.get_operate_type(i) == 'back':
                for n in range(self.yaml.get_backtimes(i)):
                    self.baseoperate.back()

            elif self.yaml.get_operate_type(i) == 'swipe_up':
                for n in range(self.yaml.get_backtimes(i)):
                    self.baseoperate.swipe_up()

    def back_home(self):
        '''
        返回至工作台
        :return:
        '''
        self.baseoperate.page('工作台')

公共部分的代码就介绍这么多,在编写这个框架的时候,大部分精力都花在这部分,所以个人觉得还是值得好好研究的

Page部分

page部分是最小用例集,一个模块一个文件夹,以客户为例,
目前写了两个用例,一个新增,一个排序,文件如下:

file.png

代码如下,非常的简洁,

import sys
reload(sys)
sys.setdefaultencoding('utf8')
import codecs,os
from public.Operate import Operate
from public.GetYaml import getyaml

PATH = lambda p: os.path.abspath(
    os.path.join(os.path.dirname(__file__), p)
)
yamlpath = PATH("../../testyaml/cm/cm-001addcm.yaml")

class AddcmPage:

    def __init__(self,driver):
        self.path = yamlpath
        self.driver = driver
        self.operate = Operate(self.path,self.driver)

    def operateap(self):
        self.operate.check_operate_type()

    def home(self):
        self.operate.back_home()

运行用例

这部分用了unittest,运行所有测试用例和生成报告。
一个模块一个用例,以客户为例:CmTest.py

from page.cm.CmAddcmPage import AddcmPage
from page.cm.CmSortcmPage import SortcmPage


from public.GetDriver import mydriver
driver = mydriver()

import unittest,time
class Cm(unittest.TestCase):

    def test_001addcm(self):
        '''
        新增客户
        :return:
        '''
        add = AddcmPage(driver)
        add.operateap()
        add.home()
    def test_002sortcm(self):
        '''
        客户排序
        :return:
        '''
        sort = SortcmPage(driver)
        sort.sortlist()
        sort.home()

    def test_999close(self):
        driver.quit()
        time.sleep(10)

if __name__ == "__main__":
    unittest.main()

首先从page层将需要运行的用例都import进来,然后用unittest运行即可。
如果想要运行所有的测试用例,需要用到runtest.py

import time,os
import unittest
import HTMLTestRunner
from testcase.CmTest import Cm


def testsuit():
    suite = unittest.TestSuite()
    suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(Cm),




])

    # runner = unittest.TextTestRunner(verbosity=2)
    # runner.run(suite)

    now=time.strftime("%y-%m-%d-%H-%M-%S")
    PATH = lambda p: os.path.abspath(
        os.path.join(os.path.dirname(__file__), p)
    )
    dirpath = PATH("./results/waiqin365-")

    filename=dirpath + now +'result.html'
    fp=open(filename,'wb')
    runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title='waiqin365 6.0.6beta test result',description=u'result:')

    runner.run(suite)
    fp.close()

if __name__ =="__main__":
    testsuit()

这边的思路差不多,也是先导入再装入suite即可

总结

就目前而言,暂时算是实现了数据与用例的分离,但是yaml的编写要求较高,不能格式上出错。
同时也有一些其他可以优化的地方,如:

  • 对弹窗的判断
  • 断开后重连机制
  • 失败后重跑机制
    等等,后续可以根据需求进行优化
    最后再贴一下开源地址Github,有兴趣的小伙伴可以去看一下,欢迎拍砖
    备注:完成过程中参考了Louis-meauto 这两个开源项目,感谢!!!


作者:迈阿密小白
链接:https://www.jianshu.com/p/00aff8435a92
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章
|
23天前
|
Java 测试技术 Android开发
Android性能测试——发现和定位内存泄露和卡顿
本文详细介绍了Android应用性能测试中的内存泄漏与卡顿问题及其解决方案。首先,文章描述了使用MAT工具定位内存泄漏的具体步骤,并通过实例展示了如何分析Histogram图表和Dominator Tree。接着,针对卡顿问题,文章探讨了其产生原因,并提供了多种测试方法,包括GPU呈现模式分析、FPS Meter软件测试、绘制圆点计数法及Android Studio自带的GPU监控功能。最后,文章给出了排查卡顿问题的四个方向,帮助开发者优化应用性能。
76 4
Android性能测试——发现和定位内存泄露和卡顿
|
14天前
|
XML Android开发 UED
💥Android UI设计新风尚!掌握Material Design精髓,让你的界面颜值爆表!🎨
随着移动应用市场的蓬勃发展,用户对界面设计的要求日益提高。为此,掌握由Google推出的Material Design设计语言成为提升应用颜值和用户体验的关键。本文将带你深入了解Material Design的核心原则,如真实感、统一性和创新性,并通过丰富的组件库及示例代码,助你轻松打造美观且一致的应用界面。无论是色彩搭配还是动画效果,Material Design都能为你的Android应用增添无限魅力。
31 1
|
23天前
|
测试技术 Shell Android开发
Android 性能测试初探 (六)
本节聊聊性能测试的最后一项- 流量,当然我所指的性能测试是针对大部分应用而言的,可能还有部分应用会关注网速、弱网之类的测试,但本系列文章都不去一一探讨了。
38 6
|
23天前
|
JavaScript 测试技术 Android开发
Android 性能测试初探 (四)
本文介绍了GPU在移动端性能测试中的重要性,并详细解释了过度绘制、帧率和帧方差的概念。针对GPU测试,文章列举了三项主要测试内容:界面过度绘制、屏幕滑动帧速率和平滑度。其中,过度绘制测试需遵循特定标准,而帧速率和平滑度测试则可通过软件或硬件方法实现。在软件测试中,使用Systrace插件和高速相机是两种常用手段。对于不同机型,帧率及帧方差的测试标准也需相应调整。
33 5
|
23天前
|
测试技术 Shell Android开发
Android 性能测试初探 (三)
本文承接《Android性能测试初探(二)》,深入探讨CPU与内存测试。介绍了移动端内存测试的重要性及其测试目标,并详细列举了不同状态下应用内存消耗情况的测试项目。此外,还提供了多种内存测试方法,包括使用`procrank`等工具的具体操作步骤。最后,文章也简要提及了CPU测试的相关内容,帮助读者更好地理解Android性能测试的关键要素。
36 5
|
23天前
|
测试技术 Shell 定位技术
Android 性能测试初探 (五)
聊聊大家不常关注的测试项- 功耗
36 3
|
23天前
|
算法 测试技术 Android开发
Android 性能测试初探 (二)
上回大体介绍了下在 android 端的性能测试项,现在我们就细节测试项做一些阐述(包括如何自己 DIY 测试)
32 4
|
23天前
|
测试技术 API Android开发
Android 性能测试初探 (一)
Android 性能测试,跟pc性能测试一样分为客户端及服务器,但在客户端上的性能测试分为 2 类: 一类为 rom 版本的性能测试;一类为应用的性能测试。
39 3
|
1月前
|
Android开发
Android学习 —— 测试init.rc中的条件触发的处理顺序
Android学习 —— 测试init.rc中的条件触发的处理顺序
|
2月前
|
存储 搜索推荐 Java
探索安卓开发中的自定义视图:打造个性化UI组件Java中的异常处理:从基础到高级
【8月更文挑战第29天】在安卓应用的海洋中,一个独特的用户界面(UI)能让应用脱颖而出。自定义视图是实现这一目标的强大工具。本文将通过一个简单的自定义计数器视图示例,展示如何从零开始创建一个具有独特风格和功能的安卓UI组件,并讨论在此过程中涉及的设计原则、性能优化和兼容性问题。准备好让你的应用与众不同了吗?让我们开始吧!

推荐镜像

更多
下一篇
无影云桌面