- 自动化测试框架
自动化测试框架:python+uiautomator2+pytest+pytest-html
- 自动化的设计模式
设计模式:POM(PageObject Model)
说明文件(readme.txt),具体如下:
- 概述
该DEMO项目用于自动化测试APP导航零号的一框搜索功能,内容如下:
- base目录
basepage.py
# 创建者: zhangxuexin
# 创建时间: :2022/5/10 15:25
import os
import time
from time import sleep
import uiautomator2 as u2
from PIL import Image
from config.config_read import config
from config.log_read import logger
class BasePage():
'''该类用于封装uiautomator2的各种操作,以作为工具类来使用'''
def __init__(self, d):
self.d = d
# 元素定位
def loction(self, attribute, loc):
'''
:param attribute:元素属性名称相当于key
:param loc: 元素属性值,相当于value
:return: 定位到的元素
'''
try:
if attribute == 'resourceId':
return self.d(resourceId=loc)
elif attribute == 'text':
return self.d(text=loc)
logger.error(f'获取元素信息为:{loc},元素属性为:{attribute},元素定位成功')
except Exception as e:
logger.error(f'获取元素信息为:{loc},元素属性为:{attribute},元素定位失败')
# app启动
def start_app(self, package_name):
self.d.app_start(package_name)
# app退出
def stop_app(self, package_name):
self.d.app_stop(package_name)
# app全部退出
def stop_allapp(self):
self.d.app_stop_all()
logger.info('退出设备所有程序')
# 点击操作
def click(self, attribute, loc):
try:
self.loction(attribute, loc).click()
logger.info(f'点击信息为:{loc},元素属性为:{attribute},点击操作成功')
except Exception as e:
logger.error(f'点击信息为:{loc},元素属性为:{attribute},点击操作失败')
# 输入字符
def send_keys(self, attribute, loc, text):
try:
self.loction(attribute, loc).send_keys(text)
logger.info(f'获取信息为:{loc},元素属性为:{attribute},输入:{text},输入操作成功')
except Exception as e:
logger.error(f'获取信息为:{loc},元素属性为:{attribute},输入:{text},输入操作失败')
# 用于截图并保存至特定目录
def image_save(self):
img = self.d.screenshot() # 进行截屏
image_name = f'{time.strftime("%Y%m%d%H%M%S")}.png' # 设置图片名称按照当前时间
image_path = "F:\u2demo_test\report\image\" + image_name # 设置图片存储路径
img.save(image_path)
# 用于捕获所定位元素是否存在的判断
def assert_text(self, attribute, loc):
'用于判断所要获取的元素是否存在'
try:
assert self.loction(attribute, loc).exists(timeout=5) # 可能发生问题的代码
logger.info(f'验证信息为:{loc},元素属性为:{attribute},验证成功')
except Exception as e:
logger.error(f'验证信息为:{loc},元素属性为:{attribute},验证失败') # 如果出现异常后要执行的代码
self.image_save() # 如果验证失败,则进行当前页面的截图
sleep(2)
- config目录
Config_read.py
# 创建者: zhangxuexin
# 创建时间: :2022/5/14 15:01
import configparser as cp
# 配置文件路径
file_path = "..config.ini"
class Config_Read():
rc = cp.ConfigParser() # 创建ConfigParser文件配置对象
rc.read(file_path, 'utf-8') # 读取配置文件目录
def read_config(self, section, value):
'''该方法用于获取配置文件中某个section的值'''
return self.rc.get(section, value)
def read_package(self):
'''该方法用于获取默认APP包名'''
return self.rc.get('Default', 'package')
def read_device_id(self):
'''该方法用于获取默认设备序列号'''
return self.rc.get('Default', 'device_id')
def read_Overall_level(self):
'''该方法用于获取默认总的日志等级'''
return self.rc.get('Default', 'Overall_level')
def read_data_address(self):
'''该方法用于获取默认数据地址'''
return self.rc.get('Default', 'data_address')
config = Config_Read()
if __name__ == '__main__':
print(config.read_device_id())
log_read.py
# 创建者: zhangxuexin
# 创建时间: :2022/5/11 21:52
import logging
import time
class My_Log():
'''该类用于进行日志的收集'''
def __init__(self, logfile_path=f"..//report/log"
f"/log[{time.strftime('%Y-%m-%d')}].txt"): # 按照日期来生成一个log文件
'''
:param logfile_path: 定义log文件输出方式的文件路径
:param log_level: 定义log日志级别信息
:param handles_level: 定义输出渠道级别信息
'''
# 创建日志收集器
self.logger = logging.getLogger(__name__) # 定义一个日志收集器
self.logger.setLevel('DEBUG') # 定义日志收集器的日志等级
# info = # 定义日志输出格式
fmt = logging.Formatter(
"[%(asctime)s]:[%(filename)s[%(lineno)d]][%(levelname)s]:%(message)s") # 定义好的日志输入格式(info)放入日志收集器
# 创建日志通过控制台输出
ch = logging.StreamHandler() # 定义控制台输出方式处理器
ch.setLevel('DEBUG') # 定义控制台输出方式日志等级
ch.setFormatter(fmt) # 将日志收集器中的日志输出格式传给控制台输出方式处理器
self.logger.addHandler(ch) # 日志收集器添加控制台输出方式处理器
# 创建日志通过文件方式输出
fh = logging.FileHandler(logfile_path,
mode='a', encoding='utf-8') # 定义文件输出方式处理器
fh.setLevel('DEBUG') # 定义文件输出方式日志等级
fh.setFormatter(fmt) # 将日志收集器中的日志输出格式传给文本输出方式处理器
self.logger.addHandler(fh) # 日志收集器添加文本输出方式处理器
def getlog(self):
return self.logger
logger = My_Log().getlog()
if __name__ == "__main__":
logger.debug('123')
logger.info('123')
logger.warning('123')
logger.error('123')
logger.critical('123')
logger.info(f"{time.strftime('%Y-%m-%d')}")
Data_read.py
# 创建者: zhangxuexin
# 创建时间: :2022/5/10 20:42
import openpyxl as op
class Data_Read():
'''此类用于读取数据文件内容'''
# 这个方法是该类的专属方法,所以使用静态方法
@staticmethod
def excle_read(address, sheetname):
wb = op.load_workbook(address)
ws = wb[sheetname]
data_list = []
for row in ws['A']:
# print(row.value)
data_list.append(row.value)
# print(data_list)
return data_list
if __name__ == '__main__':
dr = Data_Read()
dr.excle_read("..//data/address_data.xlsx", 'Sheet1')
- page_object目录
mapmain_page.py
# 创建者: zhangxuexin
# 创建时间: :2022/5/10 20:11
from base.basepage import BasePage
from config.config_read import config
from config.log_read import logger
import uiautomator2 as u2
# d = u2.connect()
# logger.info('设备连接成功')
# d.screen_on()
# logger.info('屏幕亮起')
# try:
# d.app_start(config.read_package())
# logger.info(f'成功启动:{config.read_config("Default", "package_name")}')
# d.implicitly_wait(10)
# except Exception as e:
# logger.critical(f'启动失败:{config.read_config("Default", "package_name")}')
# exit()
class Map_Page(BasePage):
'该类用于封装主地图页面的页面元素和主要业务'
# 页面元素
search_lable = "com.mapbar.navigation.zero:id/rl_topSearchBarContainer" # 一框搜索栏
Routeplan_button = "com.mapbar.navigation.zero:id/ll_enterRoutePlanView" # 路径规划按键
search_lable_text = '在此输入搜索词' # 搜索栏文本
def search(self):
'''跳转到一框搜索页面操作'''
logger.info(f'点击一框搜索栏(search_lable),元素属性为:resourceId')
self.click('resourceId', self.search_lable)
logger.info('验证信息为:在此输入搜索词,元素属性为:text')
self.assert_text('text', self.search_lable_text)
def Route_plan(self):
'''跳转到路线规划页面操作'''
self.click('resourceId', self.Routeplan_button)
if __name__ == '__main__':
mp = Map_Page(d)
mp.search()
search_page.py
# 创建者: zhangxuexin
# 创建时间: :2022/5/10 16:22
from time import sleep
from base.basepage import BasePage
from config.config_read import config
from config.log_read import logger
import uiautomator2 as u2
# d = u2.connect()
# logger.info('设备连接成功')
# d.screen_on()
# logger.info('屏幕亮起')
# try:
# d.app_start(config.read_package())
# logger.info(f'成功启动:{config.read_config("Default", "package_name")}')
# d.implicitly_wait(10)
# except Exception as e:
# logger.critical(f'启动失败:{config.read_config("Default", "package_name")}')
# exit()
class Search_Page(BasePage):
'用于封装搜索页面的页面元素和主要业务'
# 页面元素
searchframe_lable = '在此输入搜索词' # 一框搜索栏
# searchframe_lable = "com.mapbar.navigation.zero:id/et_keyWord" # 一框搜索栏
search_button = "com.mapbar.navigation.zero:id/rl_showSearchView" # 搜索按键
back_button = "com.mapbar.navigation.zero:id/iv_searchViewBack" # 返回按键
list_result = 'com.mapbar.navigation.zero:id/ll_info' # 搜索结果列表信息
def search_frame(self, text):
'''一框搜索页面操作'''
logger.info(f'在一框搜索栏(searchframe_lable),元素属性为:text,输入:{text}')
self.send_keys('text', self.searchframe_lable, text)
sleep(2)
# logger.info(f"等待了2秒")
logger.info(f'点击搜索按键(search_button),元素属性为:resourceId')
self.click('resourceId', self.search_button)
logger.info(f'验证信息为:{self.list_result},元素属性为:resourceId')
self.assert_text('resourceId', self.list_result)
logger.info(f'点击返回按键(back_button),元素属性为:resourceId')
self.click('resourceId', self.back_button)
if __name__ == '__main__':
sp = Search_Page(d)
sp.search_frame('美食')
- testcase目录
tescase.py
# 创建者: zhangxuexin
# 创建时间: :2022/5/10 17:31
import pytest
import uiautomator2 as u2
from config.config_read import config
from config.log_read import logger
from page_object.mapmain_page import Map_Page
from page_object.search_page import Search_Page
from config.data_read import Data_Read
d = u2.connect()
logger.info('设备连接成功')
d.screen_on()
logger.info('屏幕亮起')
try:
d.app_start(config.read_package())
logger.info(f'成功启动:{config.read_config("Default", "package_name")}')
d.implicitly_wait(10)
except Exception as e:
logger.critical(f'启动失败:{config.read_config("Default", "package_name")}')
exit()
class TestCase():
" 该类用于设计测试用例 "
# def setup_class(self):
# '''在所有的用例执行前执行一次'''
def teardown_class(self):
'''在所有的用例执行完成之后执行一次'''
try:
d.app_stop(config.read_package())
logger.info(f'成功关闭:{config.read_config("Default", "package_name")}')
except Exception as e:
logger.error(f'关闭失败:{config.read_config("Default", "package_name")}')
# 通过参数化测试搜索操作
def test_01(self):
'''跳转到一框搜画面用例'''
mp = Map_Page(d)
logger.info("用例开始:跳转到一框搜索画面")
mp.search()
logger.info("用例结束:跳转到一框搜索画面")
# 使用Excle文件读取数据的方式进行参数化
@pytest.mark.parametrize('data', Data_Read.excle_read(config.read_data_address(), 'Sheet1'))
def test_02(self, data):
'''进行一筐搜索'''
SP = Search_Page(d)
logger.info("用例开始:一框搜索操作")
SP.search_frame(data)
logger.info("用例结束:一框搜索操作")
if __name__ == '__main__':
pytest.main(['testcase.py'])
- config.ini
配置文件
#默认配置信息 [Default] package = com.mapbar.navigation.zero package_name = 导航零号 device_id = none Overall_level = DEBUG data_address = ..//data/address_data.xlsx #打印log的配置信息,log的值有:'DEBUG','INFO','WARNING','ERROR','CRITICAL' [loglevel] consol_level = DEBUG file_level = ERROR
- pytest.ini
pytest运行参数设置文件
[pytest] addopts = -s -v --html=..//report/report.html --capture=sys --reruns 0 testpaths = ./testcase python_files = testcase*.py python_classes = TestCase python_functions = test_*



