前言
学习UI自动化的同学都应该知道PO模式,PO共分为三层,分别是页面定位层,页面对象层,业务逻辑层。
po模式有以下几个优点:
1.易读性好
2.扩展性高
3.复用性强
4.维护性好
5.代码冗余率低
前因:让不会代码的同学也能编写自动化
思考问题:市面上不乏有录制回放,数据驱动的框架,为什么还要自己封装呢
解决问题:封装能更加贴切自己公司的项目,能更好的进行扩展,而且更能展示自身的价值
这里我就不具体讲解selenium基础方法的封装了,和PO模式一样的,没有做很大的改动
源码:https://download.csdn.net/download/qq_36076898/15724810
一.用例设计(Excel)
1.Excel sheet设计
参数说明:用于生成测试数据,数据生成通过tool自动生成。后面会具体讲解怎么使用
定位:所有的元素定位。后面会具体讲解怎么使用
登录信息配置:环境、账号的配置。后面会具体讲解怎么使用
用例:测试用例,我这里就只写了个demo。后面会仔细剖析用例
二.代码封装
1.封装读取【参数说明】sheet的代码
import random
import string
import time
import datetime
class Tool:
@staticmethod
def get_random_str(random_length=6):
"""
生成一个指定长度的随机字符串,数字字母混合
string.digits=0123456789
string.ascii_letters=abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
"""
str_list = [random.choice(string.digits + string.ascii_letters) for i in range(random_length)]
random_str = ''.join(str_list)
return random_str
@staticmethod
def get_random_letters(random_length=6):
"""
生成一个指定长度的随机字符串,纯字母
string.ascii_letters=abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
"""
str_list = [random.choice(string.ascii_letters) for i in range(random_length)]
random_letters = ''.join(str_list)
return random_letters
@staticmethod
def get_time_stamp(unit='s'):
"""
获取时间戳
:param unit: 单位 s 秒,ms 毫秒
:return:
"""
data = time.time()
if unit == 's':
result = int(data)
elif unit == 'ms':
result = int(round(data * 1000))
else:
result = data
return result
@staticmethod
def get_date():
"""
获取当前时间
:return:
"""
result = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return result
@staticmethod
def get_phone_number(key=False):
"""
随机生成电话号码
:param key: 默认生成虚假手机号,True 生成真实手机号
:return:
"""
pre_list = ["130", "131", "132", "133", "134", "135", "136", "137", "138", "139",
"147", "150", "151", "152", "153", "155", "156", "157", "158", "159",
"186", "187", "188", "189"]
if key:
result = random.choice(pre_list) + "".join(random.choice("0123456789") for i in range(8))
else:
result = random.choice(pre_list) + "".join(random.choice("0123456789") * 4) + "".join(
random.choice("0123456789") * 4)
return result
@staticmethod
def get_email():
"""生成随机邮箱"""
# 用数字0-9 和字母a-z 生成随机邮箱。
list_sum = [i for i in range(10)] + ["a", "b", "c", "d", "e", "f", "g", "h", 'i', "j", "k",
"l", "M", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z"]
email_str = ""
# email_suffix = ["@163.com", "@qq.com", "@gmail.com", "@mail.hk.com", "@yahoo.co.id", "mail.com"]
email_suffix = ["@163.com", "@qq.com"]
for i in range(10):
a = str(random.choice(list_sum))
email_str = email_str + a
# 随机拼接不同的邮箱后缀
return email_str + random.choice(email_suffix)
@staticmethod
def get_parameter(data):
"""
判断某一条excel数据中书否存在参数,若存在直接替换掉
:param data:
:return:
"""
value = data.replace('#', '')
if 'get_time_stamp_ms' in value:
test_data = getattr(Tool, value.replace('_ms', ''))('ms')
elif 'get_time_stamp_s' in value:
test_data = getattr(Tool, value.replace('_second', ''))()
else:
if hasattr(Tool, value):
test_data = getattr(Tool, value)()
else:
test_data = value
return test_data
if __name__ == '__main__':
test = Tool()
print(test.get_parameter('#get_time_stamp_second#'))
这里主要讲解下get_parameter()方法,其他方法都是生成数据的。get_parameter()方法处理用例中传的参数,根据传方法名生成想要的数据。例如:测过程中需要时间格式的数据,那么在测试用例中就可以填写:#get_date#
2.封装读取【定位】sheet的代码
import xlrd
from common.log import Log
log = Log()
class ReadExcel:
def __init__(self, fileName):
"""
new_data是最后返回的值
config.project_path.replace('\\', '/') + "/data.xlsx"
:param fileName: excel文件名路径
"""
self.fileName = fileName
# 读取excel文件夹
self.book = xlrd.open_workbook(self.fileName)
@staticmethod
def data_type(test_type, test_value):
"""
判断从excel单元格中获取的数据类型
1 string(text), 2 number, 3 date, 4 boolean
:param test_type: 类型
:param test_value: 值
:return:
"""
if test_type == 1:
"""字符串"""
return test_value
elif test_type == 2:
if '.0' in str(test_value):
"""整数"""
return int(test_value)
else:
"""浮点"""
return test_value
elif test_type == 3:
"""日期"""
date = xlrd.xldate_as_datetime(test_value, 0).strftime('%Y-%m-%d')
return date
elif test_type == 4:
"""布尔类型"""
if test_value == 1:
return True
elif test_value == 0:
return False
def stitching_element(self, sheet_name='定位'):
"""
读取excel,处理元素数据
:param sheet_name:
:return:
"""
result = {
}
# 读取excel
sheet = self.book.sheet_by_name(sheet_name)
row_value_one = sheet.row_values(0) # 第一行
# 总行数
row_num = sheet.nrows
for i in range(1, row_num):
result[sheet.cell_value(i, row_value_one.index('功能'))] = sheet.cell_value(i, row_value_one.index('元素定位'))
return result
结果如下:返回字典,【功能】作为key,【元素定位】作为vlaue
{
'登录-用户名': '//*[@placeholder="请输入内容"]', '登录-密码': '//*[@placeholder="请输入密码"]',
'登录-登录按钮': '//span[text()=" 登录 "]', '登录-错误提示': '//p[text()="用户名或者密码错误"]',
'首页-昵称': '//*[@id="app"]/section/header/div[2]/span', '首页-退出登录': '//li[text()="退出登录"]',
'首页-一级菜单': '//li[@role="menuitem"]/div/span', '首页-用户管理': '//span[text()="用户管理"]',
'首页-用户管理-用户列表': '//li/span[text()="用户列表"]', '首页-用户管理-用户列表-添加用户': '//button/span[text()="添加用户"]',
'首页-用户管理-用户列表-添加用户-用户名': '//label[text()="用户名"]/following-sibling::div[1]/div/input',
'首页-用户管理-用户列表-添加用户-昵称': '//label[text()="昵称"]/following-sibling::div[1]/div/input',
'首页-用户管理-用户列表-添加用户-邮箱': '//label[text()="邮箱"]/following-sibling::div[1]/div/input',
'首页-用户管理-用户列表-添加用户-手机号': '//label[text()="手机号"]/following-sibling::div[1]/div/input',
'首页-用户管理-用户列表-添加用户-密码': '//label[text()="密码"]/following-sibling::div[1]/div/input',
'首页-用户管理-用户列表-添加用户-确定': '//*[@id="app"]/section/section/main/div/div[4]/div/div[4]/div/div[3]/span/button[2]/span',
'首页-用户管理-用户列表-当前页的所有用户': '//tr/td[2]/div'}
3.封装读取【登录信息配置】sheet的代码
import xlrd
from common import config
from common.log import Log
log = Log()
class ReadExcel:
def __init__(self, fileName):
"""
new_data是最后返回的值
config.project_path.replace('\\', '/') + "/data.xlsx"
:param fileName: excel文件名路径
"""
self.fileName = fileName
# 读取excel文件夹
self.book = xlrd.open_workbook(self.fileName)
@staticmethod
def data_type(test_type, test_value):
"""
判断从excel单元格中获取的数据类型
1 string(text), 2 number, 3 date, 4 boolean
:param test_type: 类型
:param test_value: 值
:return:
"""
if test_type == 1:
"""字符串"""
return test_value
elif test_type == 2:
if '.0' in str(test_value):
"""整数"""
return int(test_value)
else:
"""浮点"""
return test_value
elif test_type == 3:
"""日期"""
date = xlrd.xldate_as_datetime(test_value, 0).strftime('%Y-%m-%d')
return date
elif test_type == 4:
"""布尔类型"""
if test_value == 1:
return True
elif test_value == 0:
return False
def processing_environment(self, sheet_name='登录信息配置'):
"""处理环境和账号"""
result = {
}
sheet = self.book.sheet_by_name(sheet_name)
cell_values = sheet.merged_cells
row_value_one = sheet.row_values(3)
cell_range = []
for cell_value in cell_values:
for i in range(cell_value[0], cell_value[1]):
for j in range(cell_value[2], cell_value[3]):
if '是' == sheet.cell_value(i, j):
cell_range.append(cell_value)
break
result['url'] = sheet.cell_value(cell_range[0][0], row_value_one.index('url'))
for i in range(cell_range[0][0], cell_range[0][1]):
if i == cell_range[0][0]:
result['account'] = {
"username": sheet.cell_value(i, row_value_one.index('用户名')),
"nickname": sheet.cell_value(i, row_value_one.index('昵称')),
"password": self.data_type(2, sheet.cell_value(i, row_value_one.index('密码')))
}
result[sheet.cell_value(i, row_value_one.index('用户名'))] = {
"nickname": sheet.cell_value(i, row_value_one.index('昵称')),
"password": self.data_type(2, sheet.cell_value(i, row_value_one.index('密码')))
}
result['username_text'] = sheet.cell_value(0, 1)
result['password_text'] = sheet.cell_value(1, 1)
result['login_button'] = sheet.cell_value(2, 1)
return result
结果如下:
{
'url': 'http://127.0.0.1/#/login',
'account': {
'username': 'test1', 'nickname': '测试', 'password': 123456},
'test1': {
'nickname': '测试', 'password': 123456},
'root': {
'nickname': '超级管理员', 'password': 123456},
'username_text': '登录-用户名',
'password_text': '登录-密码',
'login_button': '登录-登录按钮'
}
4.封装读取【demo】(用例)sheet的代码
import xlrd
from common import config
from common.log import Log
log = Log()
class ReadExcel:
def __init__(self, fileName):
"""
new_data是最后返回的值
config.project_path.replace('\\', '/') + "/data.xlsx"
:param fileName: excel文件名路径
"""
self.fileName = fileName
# 读取excel文件夹
self.book = xlrd.open_workbook(self.fileName)
@staticmethod
def data_type(test_type, test_value):
"""
判断从excel单元格中获取的数据类型
1 string(text), 2 number, 3 date, 4 boolean
:param test_type: 类型
:param test_value: 值
:return:
"""
if test_type == 1:
"""字符串"""
return test_value
elif test_type == 2:
if '.0' in str(test_value):
"""整数"""
return int(test_value)
else:
"""浮点"""
return test_value
elif test_type == 3:
"""日期"""
date = xlrd.xldate_as_datetime(test_value, 0).strftime('%Y-%m-%d')
return date
elif test_type == 4:
"""布尔类型"""
if test_value == 1:
return True
elif test_value == 0:
return False
def stitching_data(self, case_type='冒烟'):
"""
读取excel,处理用例数据
:return:
"""
result = []
# 获取所有sheet
sheets = self.book.sheet_names()
for sheet_name in sheets:
if sheet_name != '参数说明' and sheet_name != '定位' and sheet_name != '登录信息配置':
sheet = self.book.sheet_by_name(sheet_name)
# 用例数量
case_num_list = sheet.merged_cells[0:len(sheet.merged_cells) // 3]
# 获取第行列数据
row_value_one = sheet.row_values(0) # 第一行
# 处理数据
for i in case_num_list:
tag = sheet.cell_value(i[0], row_value_one.index('用例类型'))
if case_type == tag or case_type is True:
case = {
'description': sheet.cell_value(i[0], row_value_one.index('描述')), 'tag': tag}
step = []
for j in range(i[0], i[1]):
step.append(
{
'element': sheet.cell_value(j, row_value_one.index('定位')),
'operate': sheet.cell_value(j, row_value_one.index('操作方式')),
'data': sheet.cell_value(j, row_value_one.index('测试数据')),
'result': sheet.cell_value(j, row_value_one.index('参数标签'))
})
case['step'] = step
result.append(case)
return result
[
{
'description': '验证超级管理员能否创建用户',
'tag': '冒烟',
'step': [
{
'element': '', 'operate': '登录', 'data': '#root#', 'result': ''},
{
'element': '首页-用户管理', 'operate': '单击', 'data': '', 'result': ''},
{
'element': '首页-用户管理-用户列表', 'operate': '单击', 'data': '', 'result': ''}, {
'element': '首页-用户管理-用户列表-添加用户', 'operate': '单击', 'data': '', 'result': ''}, {
'element': '首页-用户管理-用户列表-添加用户-用户名', 'operate': '输入', 'data': '#get_random_letters#', 'result': '期望结果'},
{
'element': '首页-用户管理-用户列表-添加用户-昵称', 'operate': '输入', 'data': '#get_random_letters#', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户-邮箱', 'operate': '输入', 'data': '#get_email#', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户-手机号', 'operate': '输入', 'data': '#get_phone_number#', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户-密码', 'operate': '输入', 'data': '#get_random_letters#', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户-确定', 'operate': '单击', 'data': '', 'result': ''},
{
'element': '首页-用户管理-用户列表-当前页的所有用户', 'operate': '获取多个文本', 'data': '', 'result': '实际结果'},
{
'element': '', 'operate': '期望结果in实际结果', 'data': '', 'result': ''}]
},
{
'description': '验证超级管理员能否创建用户',
'tag': '冒烟',
'step': [
{
'element': '', 'operate': '登录', 'data': '#root#', 'result': ''},
{
'element': '首页-用户管理', 'operate': '单击', 'data': '', 'result': ''},
{
'element': '首页-用户管理-用户列表', 'operate': '单击', 'data': '', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户', 'operate': '单击', 'data': '', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户-用户名', 'operate': '输入', 'data': '#get_random_letters#', 'result': '期望结果'},
{
'element': '首页-用户管理-用户列表-添加用户-昵称', 'operate': '输入', 'data': '#get_random_letters#', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户-邮箱', 'operate': '输入', 'data': '#get_email#', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户-手机号', 'operate': '输入', 'data': '#get_phone_number#', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户-密码', 'operate': '输入', 'data': '#get_random_letters#', 'result': ''},
{
'element': '首页-用户管理-用户列表-添加用户-确定', 'operate': '单击', 'data': '', 'result': ''},
{
'element': '首页-用户管理-用户列表-当前页的所有用户', 'operate': '获取多个文本', 'data': '', 'result': '实际结果'},
{
'element': '', 'operate': '期望结果in实际结果', 'data': '', 'result': ''}
]
}
]
三.用例剖析以及设计思路讲解
代码会自动遍历获取所有sheet,【参数说明】【定位】【登录信息配置】除外,将所获取到的用例进行数据组装。
用例步骤:这个应该都会写,具体到每一步做什么。最后需要加一步这条用例通过的判断标准即可
操作方式:对应步骤所作出的操作,比如:单击、输入等,主要说一下第一步和最后一步所对应的操作。第一步操作如果是【登录】,操作方式直接填【登录】(这里登录有三步,输入用户名、输入密码、点击登录,我直接封装在代码里了,减少excel重复添加。如果有验证码,可以自行添加封装);最后一步通过方式必须按照【期望结果in实际结果】此格式书写,【期望结果in实际结果】代表期望结果在实际结果中,【期望结果=实际结果】代表期望结果等于实际结果
定位:根据【定位】sheet中【功能】写
测试数据:某一步骤需要说明数据,直接填写对应的方法即可,参考【参数说明】sheet;也可直接填写【参数标签】里所需要的参数
参数标签:主要用户获取期望结果、实际结果、后面步骤可能需要之前某个步骤的数据。
用例类型:方便执行脚本的时候区分执行那类用例
import ddt
import unittest
from common.log import Log
from common.read_excel import ReadExcel
from common.base import Base
from common.tool import Tool
from runTest import run_parser
tool = Tool()
log = Log()
case_info = run_parser()
read_excel = ReadExcel(case_info['file_name'])
case = case_info['case']
login_info = read_excel.processing_environment()
@ddt.ddt
class TestAllCase(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
log.info('<------------------用例执行开始------------------>')
def setUp(self) -> None:
self.case_name = self._testMethodName # 获取执行当前用例的 方法名
self.case = Base()
log.info('--------开始{}用例--------'.format(self.case_name))
def tearDown(self) -> None:
self.case.quit()
log.info('--------结束{}用例--------\n'.format(self.case_name))
@classmethod
def tearDownClass(cls) -> None:
log.info('<------------------用例执行结束------------------>')
@ddt.data(*case)
def test_speed(self, data):
log.info(data['description'])
test_data = {
} # 测试过程中需要的数据
desired_result = [] # 期望结果
actual_result = [] # 实际结果
for i in data['step']:
if i['operate'] == '登录':
# 登录
self.case.open(login_info['url'])
if i['data'] == '':
self.case.send_keys(element=login_info['username_text'],
text=login_info['account']['username'])
self.case.send_keys(element=login_info['password_text'],
text=login_info['account']['password'])
else:
self.case.send_keys(element=login_info['username_text'],
text=i['data'].replace('#', ''))
self.case.send_keys(element=login_info['password_text'],
text=login_info[i['data'].replace('#', '')]['password'])
self.case.click(element=login_info['login_button'])
elif '期望结果' in i['operate'] or '实际结果' in i['operate']:
# 断言
log.info("期望结果:{}".format(desired_result[0]))
log.info("实际结果:{}".format(actual_result[0]))
if '=' in i['operate']:
self.assertEqual(desired_result[0], actual_result[0])
elif 'in' in i['operate']:
self.assertTrue(desired_result[0] in actual_result[0])
else:
# 其他操作
if i['operate'] == '单击':
self.case.click(element=i['element'])
elif i['operate'] == '输入':
input_data = tool.get_parameter(i['data'])
if input_data in test_data.keys():
self.case.send_keys(element=i['element'], text=test_data[input_data])
else:
self.case.send_keys(element=i['element'], text=input_data)
if i['result'] != '' and i['result'] != '期望结果':
# 将复用的参数加入
test_data[i['result']] = input_data
elif i['result'] == '期望结果':
# 将期望结果加入
desired_result.append(input_data)
elif i['operate'] == '获取多个文本':
actual_result.append(self.case.get_texts(element=i['element']))
elif i['operate'] == '获取单个文本':
actual_result.append(self.case.get_text(element=i['element']))
elif i['operate'] == '悬停':
self.case.mouse_flight(element=i['element'])
四.批量运行生成报告
import sys
import unittest
import os
sys.path.append('../')
from common.log import Log
from common import config
from common.HTMLTestRunner_cn import HTMLTestRunner
from common.Email import SendMail
from common.read_excel import ReadExcel
import argparse
log = Log()
send_report_path = config.send_report_path
reportTitle = config.reportTitle
description = config.description
def run_parser():
"""参数运行"""
parser = argparse.ArgumentParser(description='通过cmd传入参数执行')
parser.add_argument('-t', '--case', default=True, help='用例类型')
parser.add_argument('-f', '--fileName', default=config.project_path.replace('\\', '/') + "/data.xlsx", help='用例类型')
args_case = parser.parse_args()
case_type = args_case.case
file_name = args_case.fileName
read_excel = ReadExcel(fileName=file_name)
case_info = read_excel.stitching_data(case_type)
return {
'case': case_info, "file_name": file_name}
def add_case():
"""加载所有的测试用例"""
# test_suite = unittest.defaultTestLoader.discover(start_dir=path, pattern=pattern, top_level_dir=None)
test_cases = unittest.TestSuite()
discover = unittest.defaultTestLoader.discover(start_dir=config.case_path, pattern='test*.py')
for test_suite in discover:
for test_case in test_suite:
# 添加用例到test_cases
test_cases.addTests(test_case)
log.info('测试用例总数:{}'.format(test_cases._tests.__len__()))
return test_cases
def run_html(test_suit):
"""执行测试用例,生成报告"""
# 判断存储报告的路径是否存在
if os.path.exists(config.report_path) is False:
os.makedirs(config.report_path)
with open(send_report_path, 'wb') as f:
runner = HTMLTestRunner(stream=f, title=reportTitle,
description=description,
verbosity=2, retry=1, save_last_try=True)
runner.run(test_suit)
SendMail().send()
def run():
"""运行"""
# 运行命令:python runTest.py -c 功能 -f excel文件路径
cases = add_case()
run_html(cases)
if __name__ == '__main__':
run()
1.命令运行指定类型用例
python runTest.py -t 冒烟 -f C:\Users\admin\Desktop\data.xlsx
-t:用例类型
-f:excel路径
上面命令含义:读取C:\Users\admin\Desktop\data.xlsx用例,执行冒烟测试用例
2.命令所有类型用例
默认执行全部用例
python runTest.py -f C:\Users\admin\Desktop\data.xlsx
五.调试用例
1.将用例里的任务类型随便写一个关键字:test
2.将run文件里的代码作出相应修改
3.执行run()方法
后言
以上就是我的设计思路,如若有什么问题欢迎给位留言!目前只封装了几个常见的操作,后面若需要其他操作的时候再封装!
转载:https://blog.csdn.net/qq_36076898/article/details/114533642