Sparkle CodesSparkle
项目 / web测试 / Day14

Web 自动化实战:从 Excel 与 YAML 驱动到全套 POM 实现

x
xpx
Jun 25, 2024
Editorial Insight
#Automation#DDT#Pytest#YAML

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]}"

六、 工程化避坑指南

  1. 环境隔离: 不要把数据库配置写死在代码里。优先使用 .env 或 conftest.py 全局配置。
  2. 数据原子化: 如果用例之间有依赖(如先注册后登录),务必在 setup_class 或 Fixture 中完成前置数据的准备,避免用例间的耦合。
  3. 失败重试: 对于不稳定的网络环境,集成 pytest-rerunfailures 插件,将重要的回归用例重试次数设为 2-3 次。

接续架构进阶: Pytest 架构精要:利用 Hooks 与 Allure 自动化构建故障证据链

BACK TO BLOG
The End of Interaction