1.文件路径获取

from pathlib import Path

project_root = Path(__file__).resolve().parent.parent
表达式 说明
__file__ 当前文件的路径(可能是相对路径或绝对路径,取决于执行方式)
Path(__file__) 用 pathlib.Path 对象包装该路径,便于后续使用路径操作方法
.resolve() 将路径解析为绝对路径,同时消除符号链接、.. 和 . 等,得到真实路径
.parent 获取当前路径的上一级目录(即父目录)
.parent.parent 连续获取两次父目录,也就是上两级目录(祖父目录)

2.约定私有化

self._config = self._load_config()     # 加载配置并保存

_load_config 前面的单下划线是约定,表示“这个方法是类内部使用的,外部代码不应直接调用”。

它不是真正的访问控制,外部仍然可以调用 obj._load_config(),但 IDE 会给出提示,团队也会遵循这个约定。

3.yaml文件内容读成字典类型

if not self.config_path.exists():
    raise FileNotFoundError(f"配置文件不存在: {self.config_path}")

self.config_path 应当是一个 pathlib.Path 对象(通常由 Path(config_file) 得到)。

exists() 方法判断路径是否存在(文件或目录)。

raise 就是“手动触发错误”的关键字,用于在检测到不合理条件时,主动告知调用者问题所在。

如果不存在,就抛出 FileNotFoundError,并附带清晰的错误信息,告知哪个文件缺失。

好处:尽早失败(fail-fast),避免后续 open 时抛出更模糊的异常。

with open(self.config_path, 'r', encoding='utf-8') as f:
    return yaml.safe_load(f)

使用 with 语句打开文件,确保使用完毕后自动关闭。

'r' 表示只读模式,encoding='utf-8' 指定字符编码,避免中文乱码。

yaml.safe_load(f) 将 YAML 文件内容解析为 Python 对象(通常是字典)。safe_load 比 load 更安全,因为它不会执行任意 Python 代码,防止潜在的安全问题。

直接将解析结果 return 给调用方,由调用方(如 __init__ 中的 self._config = self._load_config())保存。

过程:1.判断我们提供的config_path地址是否存在 -> 2.以读的形式把读到的内容保存到f ->

3.返回使用yaml软件包中的方法,将读到的内容f,按照字典的格式解析的对象

4.装饰器@property 

@property 是 Python 中一个非常实用的内置装饰器,它将类中的方法“伪装”成属性,让你可以像访问属性一样调用方法,同时还能在背后执行逻辑(如校验、计算、懒加载)。

class Person:
    def __init__(self, name):
        self.name = name          # 实例属性
        self.age = 18

    def greet(self):              # 实例方法
        return f"Hello, I'm {self.name}"

    def have_birthday(self):      # 方法有副作用
        self.age += 1

# 使用
p = Person("Alice")

# 访问属性
print(p.name)       # 'Alice'  → 直接取值
print(p.age)        # 18

# 调用方法
print(p.greet())    # 'Hello, I'm Alice'  → 必须加括号
p.have_birthday()   # 修改状态,没有返回值
print(p.age)        # 19

class Square:
    def __init__(self, side):
        self._side = side

    @property
    def area(self):
        return self._side ** 2

s = Square(5)
print(s.area)    # 25  → 注意没有括号,实际调用了 area 方法

5.isinstance和type的差别

type:

1.严格需要精确类型,例如序列化时只处理原生 dict 而不处理子类(避免意外属性被序列化)。
2.性能敏感且确认不需要考虑继承(比如类型比较在热循环中,但这种情况很少)。
3.调试或元编程中,需要准确知道对象的原始类,而不是它的父类。

isinstance:
1.符合面向对象的多态原则:子类应当可以替代父类,所以判断类型时应该承认子类实例也是父类类型。
2.代码更健壮:如果将来把 dict 换成 collections.UserDict 或自定义子类,isinstance 仍能正常工作,而 type 就会出错。
3.支持抽象基类和协议:比如判断“是否可迭代”用 isinstance(obj, Iterable) 比判断具体类型更通用。

class Animal:
    pass

class Dog(Animal):
    pass

dog = Dog()

print(isinstance(dog, Animal))   # True  → Dog 是 Animal 的子类
print(type(dog) is Animal)       # False → type(dog) 是 Dog,不是 Animal
print(type(dog) is Dog)          # True  → 精确匹配

6.初始化数据库连接 pymysql.connect()

    def __init__(self):
        """按 config 里 db 那段建个连接"""
        cfg = Config()
        self.conn = pymysql.connect(
            host=cfg.get("db.host", "localhost"),
            port=int(cfg.get("db.port", 3306)),
            user=cfg.get("db.user"),
            password=cfg.get("db.password"),
            database=cfg.get("db.database"),
            charset=cfg.get("db.charset", "utf8mb4"),
            cursorclass=pymysql.cursors.DictCursor,
            autocommit=True,
        )

调用 pymysql.connect() 建立与 MySQL 数据库的真实连接,并将连接对象保存到实例变量 self.conn 中。

7.查询数据库一条数据

    def query_one(self, sql, params=None):
        """执行 sql 拿一行"""
        try:
            with self.conn.cursor() as cursor:
                cursor.execute(sql, params or ())
                return cursor.fetchone()
        except Exception as e:
            log.error(f"query_one execution failed: {e}")
            raise
    def get_user_by_usernam(self, username):
        """用户名去 yonghu 表查一条,注册断言用"""
        sql = "SELECT id, yonghuming FROM yonghu WHERE yonghuming=%s"
        return self.query_one(sql, (username,))

conn.cursor() 的作用是从数据库连接对象中创建一个游标(Cursor)对象。

游标是数据库编程中的一个重要概念,相当于一个“指针”或“工作区”,用于:执行 SQL 语句(execute())、获取执行结果(fetchone()、fetchall() 等)、控制数据的逐行处理。

cursor.execute(sql, params or ()) 的作用是执行一条 SQL 语句,并附带参数。

执行 SQL:将 sql 字符串(如 "SELECT * FROM users WHERE id = %s")发送给数据库执行。

参数化查询:第二个参数提供 SQL 中占位符(%s)对应的实际值。使用参数化可以有效防止 SQL 注入,并能正确处理特殊字符。

params or ():如果传入的 params 为 None,则用空元组 () 代替,避免 execute 因收到 None 而报错;如果 params 有值(如 (10,) 或 {"id": 10}),则直接使用。

cursor.fetchone()只拿出查到数据的第一条数据,如果想拿到全部的查到的所有数据就得使用cursor.fetchall().

8.清空数据库并添加默认账户

    def reset_yonghu_table(self):
        """truncate 清空表再塞个默认账号"""
        try:
            with self.conn.cursor() as cursor:
                cursor.execute("TRUNCATE TABLE yonghu")

                insert_sql = """
                    INSERT INTO yonghu (yonghuming, mima, xingming, xingbie, youxiang, shouji, money)
                    VALUES (%s, %s, %s, %s, %s, %s, %s)
                """
                cursor.execute(insert_sql, ('pengbei', 'q1216530240', '鹏杯', '男', '1216530240@qq.com', '13800000000', 0))
            log.info("yonghu table truncated and default user (username=1, password=1) inserted.")
        except Exception as e:
            log.error(f"reset_yonghu_table failed: {e}")
            raise

只要你看得懂7,那你就很轻松看得懂8,cursor.execute("sql语句")就是执行你给的sql语句。Python 中的多行字符串字面量,使用 三个双引号 """(或三个单引号 ''')作为界定符。它的特点是字符串内容中可以包含换行符,代码中的换行和缩进会被保留,它与用单引号 '...' 或双引号 "..." 定义的字符串在运行时完全一样,只是书写方式不同,常用于长文本或格式化内容
比如 SQL 语句、HTML/JSON 模板、帮助文档等,方便阅读和维护。

9 统一单元格数据类型

class Excel:
    @staticmethod
    def _clean_cell(value):
        """格子里的数啊空啊统一搓成字符串,省得 123.0 这种坑"""
        if pd.isna(value):
            return None
        if isinstance(value, float) and value.is_integer():
            return str(int(value))
        return str(value)

print(Excel._clean_cell(12.0))  #输出是12

这段代码是一个静态方法,用于“清洗”单元格数据,主要目标是将各种输入统一转换为字符串或 None,并处理浮点数中整数值的特殊显示问题(避免 123.0 这种形式)。

@staticmethod:声明为静态方法,无需类实例或类参数,可通过 ClassName._clean_cell(x) 直接调用。

@staticmethod优点 说明
逻辑内聚 工具函数归入类,一目了然
避免命名冲突 用类名作命名空间,减少全局函数
无需实例化 类名直接调用,省去创建对象的成本
自文档化 清楚表明不依赖实例/类状态,是纯函数
内部可重用 实例方法中也可以调用,作为辅助工具
继承友好(有限) 子类可重写,但无 cls 自动识别
微小性能提升 少了参数传递绑定开销

使用 pandas.isna() 检测 value 是否为缺失值(包括 None、NaN、NaT 等)。若为缺失值,返回 None。

value.is_integer() 是 浮点数(float)对象的一个方法,用于判断该浮点数的小数部分是否为 0(即它是否代表一个整数)。

10 读取Excel表格

    @staticmethod
    def _read_and_clean(file_path, sheet_name=0):
        """读表再整表 map 一遍清洗"""
        df = pd.read_excel(file_path, sheet_name=sheet_name, dtype=object)
        return df.map(Excel._clean_cell)

使用 pandas.read_excel() 读取指定工作表。

dtype=object:强制将所有列的数据类型设为 object(即 Python 对象)。
这样做可以避免 pandas 自动推断类型(例如数字变成 int64 或 float64,日期变成 datetime),从而让后续的单元格清洗统一处理字符串、数字、空值等。

file_path:Excel 文件路径。

sheet_name=0:工作表名称或索引,默认 0(第一个工作表)。

df.map(func):将 func 应用到 DataFrame 的每个单元格(逐元素操作)。

Excel._clean_cell 是之前定义的静态清洗函数(处理 pd.isna、浮点数整数化、转字符串等)。

最终返回一个新的 DataFrame,其中每个单元格都经过了清洗。

11 用元组打包列信息

    @staticmethod
    def get_login_data_from_excel(file_path, sheet_name=0):
        """登录三列拉出来,zip 成一组组给 parametrize"""
        df = Excel._read_and_clean(file_path, sheet_name)
        data = list(zip(df['username'], df['password'], df['result']))

sheet_name=0时取Exsel的第一行数据。

zip() 的作用是将多个可迭代对象(如列表)中对应位置的元素打包成元组,返回一个迭代器。

df['username']、df['password']、df['result'] 是三个列,每个列都有相同数量的行。

zip(...) 会将每一行的三个值取出来,组成一个元组 (username, password, result)。

list(...) 将所有这样的元组收集成一个列表,最终 data 的结构类似:[('user1', 'pass1', '成功'), ('user2', 'pass2', '失败'), ...]

12 Base中使用的一些方法

12.1 创建驱动对象

    def __init__(self, driver=None):
        """没传 driver 就新开一个 Chrome"""
        if driver:
            self.driver = driver
        else:
            service = Service(
                executable_path="D:\\python\\chromedriver.exe")
            self.driver = webdriver.Chrome(service=service)
            self.driver.implicitly_wait(10)
            self.driver.maximize_window()

这段代码它的核心作用是初始化一个浏览器驱动对象(WebDriver),供类中其他方法进行网页元素定位、操作、断言等自动化工作。

raise e 是 Python 中重新抛出已捕获的异常的写法。它出现在 except 块内部,用于将之前捕获到的异常对象 e 再次向上层(调用方)抛出。

12.2处理弹窗

    def alert_text(self):
        """原生 alert"""
        try:
            alert = self.driver.switch_to.alert
            text = alert.text
            log.info(text)
            alert.accept()
            return text
        except Exception as e:
            raise e

这段代码的作用是处理浏览器中原生的 JavaScript Alert(警告框)弹窗:获取弹窗中的文本内容,记录日志,然后点击“确定”按钮(accept),最后将文本内容返回。

alert.accept():Alert 对象的方法,点击弹窗上的确定按钮。

13 标签参数化

 @pytest.mark.parametrize("username, password, result", login_test_cases)
来源:pytest 框架
作用:实现参数化测试,让同一个测试函数使用多组不同的参数运行多次。
"username, password, result":参数名列表。
login_test_cases:可迭代对象(通常是一个列表,每个元素是一个包含三个值的元组),依次为每个参数提供具体值。
效果:pytest 会为 login_test_cases 中的每一组数据生成一个独立的测试用例。例如,如果 login_test_cases 有 5 组数据,就会生成 5 个测试,分别检查用户名、密码的输入组合与预期结果(result)是否匹配。

14 allure报告结构

@allure.feature('登录注册')
来源:Allure 测试报告框架
作用:将测试用例归类到更高级的功能模块(Feature)中,便于在报告中按业务功能查看测试结果。
参数 '登录注册' 表示该测试属于“登录注册”这一核心功能。
效果:在 Allure 报告中,所有标注了 @allure.feature('登录注册') 的测试会被归拢到一起,方便分析该功能的整体测试覆盖和通过情况。

@allure.story('登录')
来源:Allure 测试报告框架
作用:在某个 Feature 下进一步划分用户故事(Story)或子场景。
参数 '登录' 表示这个测试是“登录注册”功能中“登录”这一具体用户故事的用例。
效果:Allure 报告会以 Feature → Story 的层级结构展示测试用例。例如:
登录注册 (Feature)
  └── 登录 (Story)
        ├── 测试用例1 (参数组1)
        ├── 测试用例2 (参数组2)
        └── ...

更多推荐