一、PO模式
-
v1: 不使用任何设计模式和单元测试框架
-
v2: 使用 pytest 管理用例
-
v3: 使用方法封装的思想, 对代码进行优化
-
v4: 采用PO模式的分层思想对代码进行拆分, 分离page
-
v5: 对PO分层后的代码继续优化, 分离page中的元素和操作
-
v6: PO模式深入封装, 把共同操作提取封装
结构:
Base_action.py
定义一个名为 BaseAction 的基类,其中包含一些常用的页面操作方法,这些方法可以被其他页面对象类继承和复用。 __init__(self, driver): 这是类的初始化方法,接受一个参数 driver,通常是 WebDriver 对象,用于与浏览器进行交互。 find_el(self, feature): 这个方法接受一个特征(feature)参数,通常是一个元组,包含了元素的定位方式和值。它通过 WebDriver 对象的 find_element 方法来查找页面上的单个元素,并返回找到的元素对象。 find_els(self, feature): 类似于 find_el 方法,但是它用于查找页面上符合特征的所有元素,并返回一个元素对象列表。 click(self, feature): 这个方法用于点击页面上的某个元素,它接受一个特征参数,调用 find_el 方法来查找元素,然后调用找到的元素对象的 click 方法来执行点击操作。 input(self, feature, content): 这个方法用于在页面的输入框中输入内容,它接受两个参数,一个是特征参数用于定位输入框元素,另一个是要输入的内容。它先查找到输入框元素,然后调用元素对象的 send_keys 方法来输入内容。 clear(self, feature): 这个方法用于清空输入框中的内容,它接受一个特征参数用于定位输入框元素,然后调用元素对象的 clear 方法来清空输入框中的内容。
在 Python 中,*feature 是一种语法,通常用于解包(unpacking)元组或列表中的元素。 在你的代码中,feature 是一个元组,格式类似于 (By.ID, "some_value") 或者 (By.CLASS_NAME, "some_class")。而 *feature 就是将这个元组拆解,传递给 find_element 方法。 举个例子,如果 feature 的值是 (By.ID, "username"),那么 *feature 就等价于 By.ID, "username",这样就可以直接作为参数传递给 find_element 方法使用。
login_page.py
driver_utils.py
test_login.py
二、数据驱动
JSON语法规格
-
大括号保存对象
-
中括号保存数组
-
对象和数组可以相互嵌套
-
数据采用键值对来表示
-
多个数据用逗号分隔
JSON值
-
数字 (整数或者浮点数)
-
字符串 (在双引号中)
-
逻辑值 (true 或者 false)
-
数组 (在中括号中)
-
对象 (在大括号中)
-
null
-
JSON中空值用 null 表示
-
python中对应的用 None 表示
-
import json # 把python字典类型转换为JSON字符串 dict1 = { "name": "zhangsan", "age": 18, "is_man": True, "school": None } # 使用 dumps 方法, 得到的结果是 json 字符串 json_str1 = json.dumps(dict1) print(json_str1) # 把JSON字符串转换为python字典 json_str2 = '{"name": "zhangsan", "age": 18, "is_man": true, "school": null}' # 使用 loads 方法, 得到的结果是 python字典 dict2 = json.loads(json_str2) print(dict2)
Json文件读写:
import json # 读取 data.json 文件 with open("data.json", "r", encoding="utf-8") as f: data1 = json.load(f) print(data1) # 把字典写入json文件 "data2.json" data2 = data1 with open("data2.json", "w", encoding="utf-8") as f: json.dump(data2, f) # 把字典写入json文件 "data3.json" ------解决写入中文的问题 data3 = data1 with open("data3.json", "w", encoding="utf-8") as f: json.dump(data2, f, ensure_ascii=False)
json.dump(data1,data2)
data11写入到data2中
步骤
-
编写测试用例
-
敲代码
-
采用PO模式的分层思想对页面进行封装
-
编写测试脚本
-
定义数据文件, 实现参数化
-
# 定义测试登录的方法, 实现脚本参数化 dict1 = {"username": "18800000000", "password": "123456", "code": "8888", "msg": "账号不存在!"} dict2 = {"username": "17150312012", "password": "error", "code": "8888", "msg": "密码错误!"} @pytest.mark.parametrize("params", [dict1, dict2]) def test_login(self, params): self.login_page.click_login_link() self.login_page.input_username(params["username"]) self.login_page.input_password(params["password"]) self.login_page.input_verify_code(params["code"]) self.login_page.click_login_btn() assert params["msg"] == self.login_page.get_msg()
数据解析函数:
在base包中新建 base_analyze.py 文件
import json def analyze_data(filename): with open("./data/" + filename, "r", encoding="utf-8") as f: list_data = [] dict_data = json.load(f) for value in dict_data.values(): list_data.append(value) return list_data
在 test_login.py 中进行调用, 核心代码如下:
# 定义测试登录的方法, 实现脚本参数化, 从 json文件中读取数据 @pytest.mark.parametrize("params", analyze_data("login_data.json")) def test_login(self, params): self.login_page.click_login_link() self.login_page.input_username(params["username"]) self.login_page.input_password(params["password"]) self.login_page.input_verify_code(params["code"]) self.login_page.click_login_btn() assert params["msg"] == self.login_page.get_msg()
三、日志收集
概念
日志就是用于记录程序运行时的信息, 也称为Log
作用
-
调试程序
-
了解程序运行的情况, 是否正常
-
程序运行的故障分析与问题定位
-
用来做用户行为分析和数据统计
常见日志级别
-
DEBUG: 调试级别, 打印非常详细的日志信息
-
INFO: 信息级别, 打印一般的日志信息, 突出强调程序的运行过程
-
WARNING: 警告级别, 打印警告日志信息, 表面会出现潜在的错误, 一般不影响正常使用软件
-
ERROR: 错误级别, 打印错误异常信息, 该级别的错误出现表示程序一些功能无法正常使用
-
CRITICAL: 严重错误级别, 表示程序可能无法继续运行
说明:
日志级别按严重程度从大到小: DEBUG < INFO < WARNING < ERROR < CRITICAL
当为程序指定一个日志级别后, 程序会记录所有大于等于该级别的日志, 而不仅仅是记录指定级别的日志
一般建议只使用: DEBUG , INFO , WARNING , ERROR 这四个级别
设置日志级别
logging.basicConfig(level=logging.INFO)
如何选择日志级别?
-
在开发和测试环境中, 为了尽可能详细的查看程序的运行状态, 可以使用 DEBUG或INFO级别的日志获取详细的日志信息, 但非常消耗计算机的性能
-
在生产环境(也就是线上)中, 通常只记录程序的异常和错误信息, 设置日志级别为 WARNING 和 ERROR即可, 这样可以减少服务器的压力, 也方便问题的排查
自定义格式
import logging fmt = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(funcName)s:%(lineno)d] - %(message)s" logging.basicConfig(level=logging.INFO, format=fmt) logging.debug("这是一条调试信息") logging.info("这是一条普通信息") logging.warning("这是一条警告信息") logging.error("这是一条错误信息") logging.critical("这是一条严重错误信息")
日志输出:
logging.basicConfig(filename="xxx.log")
logging日志模块四大组件
-
日志器: Logger, 提供程序使用日志的入口
-
处理器: Handler, 将logger创建的日志发送到合适的目的输出
-
格式器: Formatter, 决定日志输出的格式
-
过滤器: Filter, 决定输出哪条日志, 丢弃哪条日志
关系
-
日志器 (logger) 需要通过处理器 (handler) 将日志信息输出到目标位置
-
不同的处理器 (handler) 可以将日志输出到不同位置
-
日志器 (logger) 可以设置多个处理器 (handler) 将同一条日志记录输出到不同的位置
-
每个处理器 (handler), 都可以设置自己的格式器 (formatter) 实现同一条日志以不同的格式输出
-
每个处理器 (handler), 都可以设置自己的过滤器 (filter) 实现日志过滤
简而言之:
日志器 (Logger) 是入口, 真正干活的是处理器 (Handler), 处理器还可以通过格式器 (Formatter) 和过滤器 (Filter) 对所输出的日志内容做格式化和过滤
创建Looger对象
logger = logging.getLogger() # 默认的日志器名称为 root logger = logging.getLogger("myLogger") # 自定义日志器名称, 名称为 myLogger
创建Handler对象
在程序中不应该直接实例化和使用Handler实例, 因为Handler是一个基类, 它只定义了Handler应该有的接口, 应该使用Handler实现类来创建对象
创建方式
-
将日志消息输出到控制台: logging.StreamHandler
-
将日志消息输出到文件, 并按时间切割: logging.hanlders.TimedRotatingFileHandler
-
...
常用方法
为handler设置一个格式器对象: handler.setFormatter()
创建Formatter对象
logging.Formatter(fmt=None, datefmt=None) fmt: 消息格式化字符串, 如果不指定该参数则默认使用message的原始值 datefmt: 日期格式化字符串, 如果不指定该参数则默认使用 "%Y-%m-%d %H:%M:%S"
案例:
**说明** 可读性好的日志需具备一些共性: - 在控制台和文件都能输出 - 文件输出能够按时间切割 步骤: 1. 导包 2. 创建日志器对象 / 设置日志级别 3. 创建处理器对象: 输出到控制台 + 文件(按时间切割) 4. 创建格式器对象 5. 将格式器添加到处理器 6. 将处理器添加到日志器 7. 打印日志
# 1. 导包 import logging import logging.handlers # 2. 创建日志器对象 / 设置日志级别 # logger = logging.getLogger() # 默认日志器名称为 root logger = logging.getLogger("Jay") # 自定义日志器名称为 Jay logger.setLevel(level=logging.DEBUG) # 3. 创建处理器对象: 输出到控制台 + 文件(按时间切割) ls = logging.StreamHandler() lf = logging.handlers.TimedRotatingFileHandler(filename="172.log", when="s", backupCount=3) # 4. 创建格式器对象 fmt = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(funcName)s:%(lineno)d] - %(message)s" formatter = logging.Formatter(fmt=fmt) # 5. 将格式器添加到处理器 ls.setFormatter(formatter) lf.setFormatter(formatter) # 6. 将处理器添加到日志器 logger.addHandler(ls) logger.addHandler(lf) # 7. 打印日志 while 1: logger.debug("aaaaaaaaaaaaaaaa")
为了解决日志文件过大的问题,可以考虑以下几种方法:
- 日志轮转: 使用
RotatingFileHandler
或TimedRotatingFileHandler
对象来创建文件处理器,它们会定期轮转日志文件,以防止日志文件过大。 - 日志归档: 使用
TimedRotatingFileHandler
可以将日志文件按照日期进行归档,将旧的日志文件备份或压缩,以节省存储空间。 - 日志级别控制: 确保只记录必要的日志信息,并根据需要调整日志级别,避免记录过多的无用信息。