三、Web自动化测试(3)

一、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中
复制代码

步骤

  1. 编写测试用例

  2. 敲代码

    1. 采用PO模式的分层思想对页面进行封装

    2. 编写测试脚本

    3. 定义数据文件, 实现参数化

复制代码
 # 定义测试登录的方法, 实现脚本参数化
    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 可以将日志文件按照日期进行归档,将旧的日志文件备份或压缩,以节省存储空间。
  • 日志级别控制: 确保只记录必要的日志信息,并根据需要调整日志级别,避免记录过多的无用信息。
posted @   Mei_first  阅读(27)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
点击右上角即可分享
微信分享提示