当前位置: 首页 > news >正文

12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建

文章目录

      • 一、如何实现一条用例,实现覆盖所有用例的测试
        • 1、结合数据驱动:编辑一条用例,外部导入数据实现循环测试
        • 2、用例体:实现不同用例的操作步骤+对应的断言
      • 二、实战
        • 1、项目路径总览
        • 2、common 文件夹下的代码文件
        • 3、keywords 文件夹下的代码文件
        • 4、testcases 文件夹下的代码文件
        • 4、testdata 文件夹下的 case_data.xlsx 文件
        • 5、config.py 文件
      • 三、web 自动化测试完结-项目代码(总)

一、如何实现一条用例,实现覆盖所有用例的测试

1、结合数据驱动:编辑一条用例,外部导入数据实现循环测试
2、用例体:实现不同用例的操作步骤+对应的断言
  • 封装对应的方法,可以执行所有用例的操作步骤+断言

二、实战

通过读取 excel 数据,进行数据驱动自动化测试
通过反射函数,实现关键字驱动
即使不懂代码的人,也能通过编辑 excel 数据进行测试

1、项目路径总览

2、common 文件夹下的代码文件

2.1 excel_operator.py 文件
是对 excel 操作进行封装的方法

import os.path
import timeimport openpyxl
from TestKDT import config
from openpyxl.styles import Font,PatternFill,colorsclass ExcelOperator:"""操作 excel 文件"""def __init__(self,filename=os.path.join(config.testdata_dir,"case_data.xlsx")):# 获取到测试用例 excel 的文件路径self.file_path = filename# 获取到测试用例 excel 工作簿self.wk = openpyxl.load_workbook(filename)def get_case_data(self):# """获取 excel 工作簿中所有工作表的数据"""# self.sheetnames = self.wk.sheetnames"""获取 excel 工作簿中要执行的用例工作表的数据"""self.sheetnames = self.get_cases_name()values= []# 循环每个工作表for sheet_name in self.sheetnames:# 获取某个工作表某个区间列的数据value = self.get_startcol_endcol_value(sheet_name)case_data = {'case_name': sheet_name, 'steps_data': value}values.append(case_data)return values# 获取执行用例的工作表名称def get_cases_name(self):# 获取汇总用例的工作表cases_sheet = self.wk[config.cases_sheet_name]cases_name = []# 根据是否执行,取到用例的名称for row in range(2,cases_sheet.max_row+1):# 循环汇总表每一行数据is_execute = cases_sheet.cell(row, config.case_is_execute).valueif is_execute=="y":case_name = cases_sheet.cell(row, config.case_name).valuecases_name.append(case_name)return cases_name# 获取某个工作表某个区间列的数据def get_startcol_endcol_value(self,sheetname,startcol=config.keyword_col,endcol=config.action_col):# 获取工作表sheet = self.wk[sheetname]values = []# 循环对应工作表中的每一行数据for row in range(2,sheet.max_row+1):step_data = []for col in range(startcol,endcol+1):value = sheet.cell(row=row,column=col).valueif value is not None:step_data.append(value)values.append(step_data)return values# 将测试步骤的结果写入 excel 文件的用例工作表中def write_step_result(self, sheet_name, row, col, result):"""写入测试步骤的结果"""case_sheet = self.wk[sheet_name]# 写入每步操作步骤结束时间self.write_current_time(case_sheet,row,config.step_end_time)# 写入测试步骤的结果case_sheet.cell(row, col).value = result# 颜色填充 绿色通过 红色失败red_fill = PatternFill(fill_type="solid", fgColor="00FF0000")green_fill = PatternFill(fill_type="solid", fgColor="0000FF00")if result == 'FAIL':case_sheet.cell(row, col).fill = red_fillelse:case_sheet.cell(row, col).fill = green_fillself.wk.save(self.file_path)# 将测试用例的结果写入 excel 文件的用例汇总表中def write_cases_result(self, case_name, sheet_name=config.cases_sheet_name, col=config.case_result,):"""写入测试用例的结果"""cases_sheet = self.wk[sheet_name]for row in range(2,cases_sheet.max_row+1):# 循环汇总表的每一行col_case_name = cases_sheet.cell(row, config.case_name).valueif col_case_name == case_name:# 获取执行的用例case_name = cases_sheet.cell(row, config.case_name).valuesheet = self.wk[case_name]# 写入每条测试用例的结束时间self.write_current_time(cases_sheet, row, config.case_end_time)# 获取每条执行用例的结果steps_result = self.get_sheet_col_value(sheet=sheet, col=config.step_result)# 颜色填充 绿色通过 红色失败red_fill = PatternFill(fill_type="solid", fgColor="00FF0000")green_fill = PatternFill(fill_type="solid", fgColor="0000FF00")# 将结果写入汇总表中if 'FAIL' in steps_result:cases_sheet.cell(row, col).value = 'FAIL'cases_sheet.cell(row, col).fill = red_fillelse:cases_sheet.cell(row, col).value = 'PASS'cases_sheet.cell(row, col).fill = green_fillself.wk.save(self.file_path)def get_sheet_col_value(self, sheet, col):"""获取某个工作表某列的所有数据"""values = []for row in range(2, sheet.max_row + 1):step_col_value = sheet.cell(row, col).valuevalues.append(step_col_value)return valuesdef write_current_time(self, sheet, row, col):"""将当前时间写入某个表格中"""current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())sheet.cell(row, col).value = current_timeif __name__ == '__main__':# print(ExcelOperator().get_startcol_endcol_value("登录",3,6))print(ExcelOperator().get_cases_name())

2.2、logger.py 文件
和之前 POM 的日志一样

import logging
import os
import time
from TestKDT import configclass FrameLogger:def get_logger(self):# 创建日志器logger = logging.getLogger("logger")# 日志输出当前级别及以上级别的信息,默认日志输出最低级别是warningif not logger.handlers:logger.setLevel(logging.INFO)# 创建控制台处理器----》输出控制台SH = logging.StreamHandler()# 创建文件处理器----》输出文件log_path = os.path.join(config.logs_dir, f"log_{time.strftime('%Y%m%d%H%M%S', time.localtime())}.txt")FH = logging.FileHandler(log_path,mode="w",encoding="utf-8")# 日志包含哪些内容    时间  文件  日志级别 :事件描述/问题描述formatter = logging.Formatter(fmt="[%(asctime)s] [%(filename)s] %(levelname)s :%(message)s",datefmt='%Y/%m/%d %H:%M:%S')logger.addHandler(SH)logger.addHandler(FH)SH.setFormatter(formatter)FH.setFormatter(formatter)return logger
3、keywords 文件夹下的代码文件

3.1 library.py 文件
是对各种关键字函数的封装

import os
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from TestKDT import config
from TestKDT.common.logger import FrameLogger as logclass Library:# 登录用例 = 多个操作步骤组成 基于每个操作步骤封装对应的关键字函数# 登录用例:# 1、打开浏览器- 关键字函数- open_browser()# 2、加载项目地址- 关键字函数- load_url()# 3、输入用户名- 关键字函数- input()# 4、输入密码- 关键字函数- input()# 5、点击登录- 关键字函数- click()def __init__(self):self.logger = log().get_logger()def open_browser(self,browser):"""打开浏览器"""# 传入的浏览器参数保持首字母大写browser = browser.capitalize()# 获取不同类型的浏览器驱动try:self.driver = getattr(webdriver, browser)()self.logger.info(f"打开{browser}浏览器成功")except:self.logger.error(f"打开{browser}浏览器失败")raisedef load_url(self, url):"""加载地址"""try:self.driver.get(url)self.logger.info(f"加载项目地址{url}成功")except:self.logger.error(f"加载项目地址{url}失败")raise# 等待元素可见def wait_ele_visibility(self, page_name, loc, timeout=15, poll_fre=0.5):try:WebDriverWait(self.driver, timeout, poll_fre).until(EC.visibility_of_element_located(loc))# WebElement对象self.logger.info(f"在[{page_name}]页面,找到元素:{loc}可见")except:self.logger.error(f"在[{page_name}]页面,未找到元素:{loc}可见!!!")raise# 等待元素存在def wait_ele_presence(self, page_name, loc, timeout=15, poll_fre=0.5):try:WebDriverWait(self.driver, timeout, poll_fre).until(EC.presence_of_element_located(loc))self.logger.info(f"在[{page_name}]页面,找到元素:{loc}存在")except:self.logger.error(f"在[{page_name}]页面,未找到元素:{loc}存在!!!")raisedef locator(self, page_name, by_type, express):# 定位元素try:el = self.driver.find_element(by_type, express)self.logger.info(f"在[{page_name}]页面,通过[{by_type}]方法和[{express}]语句,定位元素成功")except:self.save_screenshot(by_type)self.logger.error(f"在[{page_name}]页面,通过[{by_type}]方法和[{express}]语句,定位元素失败!!!")raisereturn eldef input(self, page_name, by_type, express, text):"""输入"""loc = (by_type, express)try:self.wait_ele_visibility(page_name, loc)self.locator(page_name,by_type, express).send_keys(text)self.logger.info(f"在[{page_name}]页面,元素{loc}输入:{text} 成功!")except:self.logger.error(f"在[{page_name}]页面,元素{loc}输入失败!")# 失败截图self.save_screenshot(page_name)raisedef click(self, page_name, by_type, express):"""点击"""loc = (by_type, express)try:self.wait_ele_visibility(page_name, loc)self.locator(page_name,by_type, express).click()self.logger.info(f"在[{page_name}]页面,元素{loc}点击成功!")except:self.logger.error(f"在[{page_name}]页面,元素{loc}点击失败!")# 失败截图self.save_screenshot(page_name)raisedef move_element(self, page_name, by_type, express):"""移动鼠标"""loc = (by_type, express)try:self.wait_ele_visibility(page_name, loc)el = self.locator(page_name, by_type, express)# 将鼠标移到元素上ActionChains(self.driver).move_to_element(el).perform()self.logger.info(f"在[{page_name}]页面,鼠标移到元素{loc}成功!")except:self.logger.error(f"在[{page_name}]页面,鼠标移到元素{loc}失败!")# 失败截图self.save_screenshot(page_name)raisedef assert_text(self,page_name, by_type, express, expect):"""断言"""loc = (by_type, express)try:self.wait_ele_visibility(page_name, loc)el = self.locator(page_name, by_type, express)fact = el.textself.logger.info(f"在[{page_name}]页面,元素{loc}文本内容获取成功!")except:self.logger.error(f"在[{page_name}]页面,元素{loc}文本内容获取失败!")# 失败截图self.save_screenshot(page_name)raiseif fact == expect:passelse:raise Exception(f"在[{page_name}]页面,断言失败,assertText:{fact} != {expect}")def save_screenshot(self,img_name):file_name = os.path.join(config.screenshots_dir, img_name+'.png')self.driver.save_screenshot(file_name)self.logger.error(f"失败截图,截取当前网页,存储的路径:{file_name}")# 封装方法,可以调用当前类下的所有关键字函数# *args:不定长参数def run(self,keyword,*args):print(keyword, args)# 实现打开浏览器# keyword = "open_browser"# args = ("edge",)# 调用关键字函数,基于反射getattr(self, keyword)(*args)
4、testcases 文件夹下的代码文件

4.1 test_case.py 文件
编写测试用例

import time
import unittest
from selenium.webdriver.common.by import By
from TestKDT.keywords.library import Library
from ddt import ddt,data,file_data,unpack
from TestKDT.common.excel_operator import ExcelOperator
from TestKDT import config@ddt
class TestCase01(unittest.TestCase):# 如何实现一条用例,实现覆盖所有用例的测试# 每条用例的数据: 关键字函数 + 测试数据# case_data = [["open_browser","edge"],["load_url","http://116.62.63.211/shop/user/logininfo.html"],#               ["input",(By.NAME, "accounts"), "hc_test"],#               ["input",(By.XPATH, '//input[@type="password"]'),"hctest123"],#               ["click",(By.XPATH, '//button[text()="登录"]')]]excel = ExcelOperator()case_data = excel.get_case_data()@data(*case_data)def test_cases(self,case_data):# 打印每条用例的数据print(case_data)case_name = case_data['case_name']steps_data = case_data['steps_data']# 用例体 = 操作步骤+ 断言# 封装对应的方法,可以执行所有用例的操作步骤 + 断言lib = Library()for index, step_data in enumerate(steps_data):try:# 执行用例的操作步骤lib.run(*step_data)# 当前执行步骤为 PASS# index  0  行数 2  因为第一行不是测试数据,而是列说明self.excel.write_step_result(sheet_name=case_name, row=index+2, col=config.step_result, result="PASS")except Exception as error:# 当前执行步骤为 FAILself.excel.write_step_result(sheet_name=case_name, row=index+2, col=config.step_result, result="FAIL")self.excel.write_cases_result(case_name)
4、testdata 文件夹下的 case_data.xlsx 文件
  • 4.1 用例汇总统计
  • 4.2 登录成功
  • 4.3 登录失败-1
  • 4.4 登录失败-2
5、config.py 文件

是项目的路径以及其他数据内容

import os# 根路径
base_dir = os.path.dirname(os.path.abspath(__file__))
# 用例路径
testcases_dir = os.path.join(base_dir, 'testcases')
# 数据路径
testdata_dir = os.path.join(base_dir, 'testdata')
# 测试报告路径
reports_dir = os.path.join(base_dir, 'outputs/reports')
# 日志路径
logs_dir = os.path.join(base_dir, 'outputs/logs')
# 失败截图
screenshots_dir = os.path.join(base_dir, 'outputs/screenshots')
# 测试用例 excel 中关键字所在列
keyword_col = 3
# 测试用例 excel 中操作值所在列
action_col = 7
# 用例汇总表名称
cases_sheet_name = "用例汇总统计"
# 用例汇总表中是否执行所在列数
case_is_execute = 5
# 用例汇总表中用例名所在列数
case_name = 2
# 用例汇总表中测试结果所在列数
case_result = 7
# 用例汇总表中测试结束时间所在列数
case_end_time = 6
# 用例工作表中执行结果所在列数
step_result = 9
# 用例工作表中执行时间所在列数
step_end_time = 8# ---调试
print(testdata_dir)

三、web 自动化测试完结-项目代码(总)

Test-Web.zip

相关文章:

  • 在 Neo4j 中实现向量化存储:从文本到高效语义搜索
  • asp.net IHttpHandler 对分块传输编码的支持,IIs web服务器后端技术
  • ROS2学习(5)------ROS2 功能包介绍
  • Neo4j 图书馆借阅系统知识图谱设计
  • 【学习笔记】因果推理导论第1课
  • NDK19无法在AppleM芯片运行解决方案
  • 用 Rust 带你了解 TCP 和 UDP
  • 协议不兼容?Profinet转Modbus TCP网关让恒压供水系统通信0障碍
  • pytorch 14.3 Batch Normalization综合调参实践
  • 【数据结构】手撕AVL树(万字详解)
  • JAVA:Spring Boot 集成 RDF4J 实现欺诈检测的技术指南
  • 源码与二进制包区别
  • 移除链表元素数据结构oj题(力扣题206)
  • 【笔记】记一次PyCharm的问题反馈
  • 图像处理:预览并绘制图像细节
  • PT2031单触控单输出触摸IC
  • 快速选择算法:优化大数据中的 Top-K 问题
  • Ubuntu系统安装docker仓库教程
  • Java微服务架构实战:Spring Boot与Spring Cloud的完美结合
  • Python 3.13.3 安装教程
  • 张国清将赴俄罗斯举行中俄“长江—伏尔加河”地方合作理事会第五次会议和“东北—远东”政府间合作委员会双方主席会晤
  • 上交所五方面落实募资新规:强化关键少数责任和股东权利保障
  • 中日东三省问题的源起——《1905年东三省事宜谈判笔记》解题
  • 押井守在30年前创造的虚拟世界何以比当下更超前?
  • 男子不满和睦家医院手术效果还遇到了“冒牌医生”?院方回应
  • 山东鄄城发生一起交通事故,造成4人死亡、2人受伤