Appium自动化测试 进阶

  • Post author:
  • Post category:其他


所谓功能测试,其实无非是将被测APP的功能都使用一遍,看有没有出现报错,或者其他不应出现的结果。

上一个脚本已经实现了通过代码来使用APP的一些功能,理论上其实是已经在测试了,但还远谈不上“自动化”。因为,不是说不用手亲自去屏幕上一个功能一个功能地点点点就叫自动化,自动化是一种思想,一种将测试规范化,模块化的处理。前期的开发需要一些工作,但后期的维护,修改会减少很多工作量,自动化是一种长期收益。假如按照上一个脚本的模式来开发测试,那还是相当于在手动测试,只不过是在手机屏幕上点点点和在电脑键盘上敲敲敲的区别罢了。

为了能实现自动化,需要进一步贯彻“公用部分可以做模块化封装”的编程思想,将APP更多的功能封装起来。同时,还要引入“架构”的理念。自动化测试的代码是一层层堆叠的,最终需要run的代码可能就几小行,但是支持这几小行的“地基”,尤为重要。另外,还需要调用专门做测试的模块,如unittest,这样便有了测试用例,测试结果,测试报告等比较规范的内容形式。

下面的代码,是按照以上的理念,在上一个脚本的基础上,优化而来。

  1. appium服务器配置,desired_capability.yaml
platformName: Android
deviceName: 127.0.0.1:62001
platforVersion: 7.1.2
# automationName: uiautomator2
# app: C:\Users\Evan\Desktop\Appium\APK\weibo.apk

# appPackage: com.sina.weibo
# appActivity: com.sina.weibo.SplashActivity

appPackage: com.baidu.BaiduMap
appActivity: com.baidu.baidumaps.WelcomeScreen

# noReset: True
noReset: False
unicodeKeyboard: True
resetKeyboard: True

ip: 127.0.0.1
port: 4723
  1. 有关日志采集的配置,log.conf,略。
  2. 读取文件-1和文件-2的脚本, desired_caps.py
#此代码是为了封装APP启动配置信息,log存储配置信息,方便日后开发自动化测试程序时调用,而且方便代码的维护修改

import yaml
from appium import webdriver

import logging
import logging.config

CON_LOG = 'log.conf'        #把日志采集器配置定义为一个常量
logging.config.fileConfig(CON_LOG)
logger = logging.getLogger()

def appium_desired():

    file = open('desired_capability.yaml', 'r')   #Appium服务器的启动配置,和APP的启动配置都封装在YAML文件中了,便于维护
    data = yaml.load(file, Loader=yaml.FullLoader)
    file.close()

    desired_caps = {}
    desired_caps['platformName']= data['platformName']
    desired_caps['deviceName']= data['deviceName']
    desired_caps['platforVersion']= data['platforVersion']
    # desired_caps['automationName']= data['automationName']

    # desired_caps['app']= data['app']
    desired_caps['appPackage']= data['appPackage']
    desired_caps['appActivity']= data['appActivity']

    desired_caps['noReset']= data['noReset']
    desired_caps['unicodeKeyboard']= data['unicodeKeyboard']
    desired_caps['resetKeyboard']= data['resetKeyboard']


    driver=webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub',desired_caps)     #appium服务器需要通过这个“WebDriver”来控制安卓设备
    driver.implicitly_wait(2)

    return driver  #把driver 返回出来,方便后续其他脚本调用

if __name__ == "__main__":    #这行代码用于调试此脚本,而且只有在此脚本内调试运行时,其下面的代码才会执行。如果此脚本被其他代码调用时,其下面的代码不会执行

    appium_desired()
  1. 基类。将APP一些最基本的功能封装成一个类,baseView.py
#此代码是为了封装一个基本类,用于driver的初始化,以及封装定位元素的方法。后面还会封装一些公共类,会继承此类

class BaseView(object):

    def __init__(self, driver):     #有1个初始化参数

        self.driver = driver

    def find_Element(self, *loc):

        return self.driver.find_element(*loc)

  1. APP共用功能的封装脚本,common_Fun.py
#此代码封装了一些公共的方法,例如启动APP后跳过开机页面,上滑,下滑,屏幕缩放等
import time
from baseView import *
from desired_caps import appium_desired, logger
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

class Common(BaseView):

    #以元组的方式定义好这2个button的定位方式和对应的属性值
    nextBtn = (By.ID, 'com.baidu.BaiduMap:id/e1')
    enterBtn = (By.ID, 'com.baidu.BaiduMap:id/a6e')
    #获得屏幕的尺寸信息
    def getsize(self):

        x = self.driver.get_window_size()['width']
        y = self.driver.get_window_size()['height']
        
        return x,y

    #进入APP
    def enterapp(self):
        logger.info('start app...')

        try:
        	self.find_Element(*self.nextBtn).click()      #此处的find_Element方法来自基类里封装的,因为Common继承了基类,可以直接调用
            #self.driver.find_element(*self.nextBtn).click()     #此处的find_element方法和基类里封装的find_Element方法无关,是“WebDrive”自身的方法
            
        except NoSuchElementException:
            logger.info('no such btn')

            try:
                self.driver.find_element(*self.enterBtn).click()

            except NoSuchElementException:
                logger.info('No such btn')

            else:
                logger.info('Enter APP successfully ')

        else:
            logger.info('enter app successfully ')

    #向上滑动屏幕
    def swipe_up(self):
        logger.info('up slide')

        # x = self.driver.get_window_size()['width']
        # y = self.driver.get_window_size()['height']
        # self.driver.swipe(int(x*0.5),int(y*0.2),int(x*0.5),int(y*0.8),1000)
        s = self.getsize()
        self.driver.swipe(int(s[0]*0.5),int(s[1]*0.2),int(s[0]*0.5),int(s[1]*0.8),1000)
        
    #向下滑动屏幕
    def swipe_down(self):
        logger.info('down slide')

        s = self.getsize()
        self.driver.swipe(int(s[0]*0.5),int(s[1]*0.8),int(s[0]*0.5),int(s[1]*0.2),1000)

    #向左滑动屏幕
    def swipe_left(self):
        logger.info('left slide')

        s = self.getsize()
        self.driver.swipe(int(s[0]*0.2),int(s[1]*0.5),int(s[0]*0.8),int(s[1]*0.5),1000)

    #向右滑动屏幕
    def swipe_right(self):
        logger.info('right slide')

        s = self.getsize()
        self.driver.swipe(int(s[0]*0.8),int(s[1]*0.5),int(s[0]*0.2),int(s[1]*0.5),1000)


if __name__ == "__main__":
    
    drv = appium_desired()      #执行appium_desired,并将返回的结果(是一个WebDriver)赋给drv这个变量

    fun = Common(drv)    #因为Common类是继承自BaseView类的,而BaseView类有定义一个初始化参数,所以这里实例化Common类时需要传入1个参数
                         #而这个参数,就是为了实现appium自动化测试所需要的那个“WebDriver”
    fun.enterapp()
    time.sleep(15)
    fun.swipe_up()
    time.sleep(2)
    fun.swipe_down()
    time.sleep(2)
    fun.swipe_left()
    time.sleep(2)
    fun.swipe_right()


  1. APP要被测试的功能脚本,这里测试百度地图的地名搜索功能,BaiduMap_search.py
from desired_caps import appium_desired, logger
from common_Fun import Common
from selenium.webdriver.common.by import By

class SearchMap(Common):

    search_box = (By.ID, 'com.baidu.BaiduMap:id/to')
    search_editor = (By.ID, 'com.baidu.BaiduMap:id/c6a')
    search_btn = (By.ID, 'com.baidu.BaiduMap:id/c68')

    def search_action(self, location):

        self.enterapp()     #因为继承了Common类,所以它所含的方法可以直接在这里调用

        logger.info('===start search===')
        logger.info(f'the searched location is {location}')

        self.driver.find_element(*self.search_box).click()
        self.driver.find_element(*self.search_editor).send_keys(location)   #在搜索框内输入地名,地名在自定义search_action方法时已被定义成了它的一个参数
        
        logger.info('===click to search===')

        self.driver.find_element(*self.search_btn).click()

        logger.info('===search finished===')

if __name__ == "__main__":
    
    drv = appium_desired()
    p = SearchMap(drv)      #因为SearchMap是继承自Common类的,Common类又继承自BaseView类,所以它们都有1个初始化参数,在实例化时都需要传入这个参数
    p.search_action('深圳')
    
  1. 测试前的环境准备和测试后的环境还原的封装脚本,setUp_tearDown.py
import time
import unittest
from desired_caps import appium_desired, logger


#定义一个类,将APP测试前、后的动作封装进去
class StartEnd(unittest.TestCase):
    
    #测试前的环境准备,setUp是内置的方法
    def setUp(self):

        logger.info('===set up===')
        self.drv = appium_desired()     #这一步是测试前的准备,即启动APP。并把appium_desired执行后的返回值初始化给到self.drv,drv可自己命名


    #测试完成后环境的还原,tearDown是内置的方法
    def tearDown(self):

        logger.info('===tear down===')
        time.sleep(10)
        self.drv.close_app()            #关闭APP。用setUp里初始化生成的self.drv的close_app方法

  1. 测试用例脚本,即真正执行测试的脚本,APP_test_script.py
import time
import unittest
from setUp_tearDown import StartEnd
from BaiduMap_search import SearchMap
from desired_caps import logger

#定义一个测试类,继承于StartEnd,就可以不用在这里再写setUp 和tearDown了,只在这里写测试用例,使得代码层次结构比较清晰
class TestSearch(StartEnd):
    
    def test_search(self):      #测试用例名称必须以test开头

        logger.info('===test location search===')
        p = SearchMap(self.drv)     #实例化SearchMap,这里的参数drv来自于StartEnd,因为TestSearch继承了StartEnd。
                                    #其实如果不继承,这个脚本里就需要写setUp和tearDown,效果是一样的。
                                    #所以理论上说,测试用例里SearchMap实例化时的参数,就是StartEnd里的setUp里命名的,用于接收appium_desired执行后的返回值的那个变量!
        p.search_action('深圳')

        

    
if __name__ == "__main__":
    
    # suite = unittest.TestSuite()              #创建一个test suite,用于装载test case
    # suite.addTest(TestSearch('test_search'))  #将测试类里面的test case装载到test suite中去

    # runner = unittest.TextTestRunner()        #创建一个test runner,用于执行test case
    # runner.run(suite)                         #正式执行test suite里的test case

    unittest.main()     #测试用例执行,相当于上面4行代码                 

纵观整个代码,所理解的自动化测试架构,简单来说就是先用python结合appium服务器写出要测试的功能(自定义一个类,然后将要测试的功能定义成一个方法包含在这个类里),然后用python中的unittest模块写出测试用例对之前写好的功能脚本中的功能函数(即代码中的方法)进行测试。

以上。



版权声明:本文为weixin_46413400原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。