所谓功能测试,其实无非是将被测APP的功能都使用一遍,看有没有出现报错,或者其他不应出现的结果。
上一个脚本已经实现了通过代码来使用APP的一些功能,理论上其实是已经在测试了,但还远谈不上“自动化”。因为,不是说不用手亲自去屏幕上一个功能一个功能地点点点就叫自动化,自动化是一种思想,一种将测试规范化,模块化的处理。前期的开发需要一些工作,但后期的维护,修改会减少很多工作量,自动化是一种长期收益。假如按照上一个脚本的模式来开发测试,那还是相当于在手动测试,只不过是在手机屏幕上点点点和在电脑键盘上敲敲敲的区别罢了。
为了能实现自动化,需要进一步贯彻“公用部分可以做模块化封装”的编程思想,将APP更多的功能封装起来。同时,还要引入“架构”的理念。自动化测试的代码是一层层堆叠的,最终需要run的代码可能就几小行,但是支持这几小行的“地基”,尤为重要。另外,还需要调用专门做测试的模块,如unittest,这样便有了测试用例,测试结果,测试报告等比较规范的内容形式。
下面的代码,是按照以上的理念,在上一个脚本的基础上,优化而来。
- 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
- 有关日志采集的配置,log.conf,略。
- 读取文件-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()
- 基类。将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)
- 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()
- 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('深圳')
- 测试前的环境准备和测试后的环境还原的封装脚本,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方法
- 测试用例脚本,即真正执行测试的脚本,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 版权协议,转载请附上原文出处链接和本声明。