Web 自动化实战:从 Excel 与 YAML 驱动到全套 POM 实现
在掌握了基础的 Page Object Model (POM) 后,真正的挑战是应对「数据爆炸」。当一个账户模块需要覆盖 50 种异常场景时,手动编写 50 个 test_ 函数显然是不可接受的成本。本文探讨如何将 YAML 数据驱动 (DDT) 与 关键字 (Keywords) 机制 融合。
一、 数据源:为什么 YAML 是工程首选?
在工业级项目中,我们优先选择 YAML 而非 Excel:
- 版本控制友好: YAML 是纯文本,Git Diff 能清晰显示谁修改了哪个测试数据。
- 嵌套结构: 天生支持 List/Dict,完美映射到 Python 的数据类型。
- 解耦逻辑: 测试步骤在代码中,测试数据在 YAML 中,实现真正的「数据驱动」。
二、 关键字模块 (Keywords):面向行为的封装
为了让 YAML 能够「驱动」Selenium,我们需要一个关键字转换器。这个类封装了显式等待逻辑,确保脚本的健壮性。
PYTHON
# keywords.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class WebKeywords:
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
def open_url(self, url):
self.driver.get(url)
def input_text(self, locator_type, locator_val, text):
"""精准输入:等待 -> 清理 -> 输入"""
by = getattr(By, locator_type.upper())
ele = self.wait.until(EC.presence_of_element_located((by, locator_val)))
ele.clear()
ele.send_keys(text)
def click_element(self, locator_type, locator_val):
"""精准点击:等待可点击后再触发"""
by = getattr(By, locator_type.upper())
ele = self.wait.until(EC.element_to_be_clickable((by, locator_val)))
ele.click()
def get_text(self, locator_type, locator_val):
by = getattr(By, locator_type.upper())
return self.wait.until(EC.visibility_of_element_located((by, locator_val))).text
三、 数据驱动实战:Pytest 参数化
利用 @pytest.mark.parametrize,我们可以实现「一个脚本,百次运行」。
PYTHON
# test_login_ddt.py
import pytest
import allure
import yaml
from .keywords import WebKeywords
def load_yaml(path):
with open(path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
@allure.feature("登录模块")
@allure.story("数据驱动测试")
@pytest.mark.parametrize("case", load_yaml("data/login_cases.yaml"))
def test_login_flow(driver, case):
allure.dynamic.title(case['case_name'])
kw = WebKeywords(driver)
with allure.step("1. 访问登录页"):
kw.open_url("http://novel.hctestedu.com/user/login.html")
with allure.step(f"2. 输入账号: {case['data']['username']}"):
kw.input_text("id", "txtUName", case['data']['username'])
kw.input_text("id", "txtPassword", case['data']['password'])
with allure.step("3. 点击登录"):
kw.click_element("id", "btnLogin")
with allure.step("4. 验证结果"):
actual_msg = kw.get_text("id", "LabErr")
assert actual_msg == case['check']['msg']
四、 证据链可见性:Allure 报告集成
仅有断言是不够的。在 CI 环境下,我们需要知道为什么失败。
通过上面的 allure.step 装饰,每一个 DDT 循环都会生成独立的步骤日志和截图(如果配置了 Hook)。在报告中,你可以清晰地看到:
- 失败的是哪一条 YAML 数据。
- 哪一步出现超时或断言失败。
- 失败瞬时的页面快照。
五、 深度验证:UI + 数据库 (Dual Verification)
当 UI 验证完成后,真正的「工业级」自动化会检查后端数据是否存在。
PYTHON
import pymysql
@allure.step("数据库状态校验")
def assert_db_state(sql, expected):
# 此处应封装为独立配置
db = pymysql.connect(host='...', user='...', password='...', database='...')
cursor = db.cursor()
cursor.execute(sql)
result = cursor.fetchone()
assert str(result[0]) == str(expected), f"DB 校验失败:预期 {expected}, 实际 {result[0]}"
六、 工程化避坑指南
- 环境隔离: 不要把数据库配置写死在代码里。优先使用
.env或conftest.py全局配置。 - 数据原子化: 如果用例之间有依赖(如先注册后登录),务必在
setup_class或 Fixture 中完成前置数据的准备,避免用例间的耦合。 - 失败重试: 对于不稳定的网络环境,集成
pytest-rerunfailures插件,将重要的回归用例重试次数设为 2-3 次。