4.1 pytest集成与断言体系


4.1 pytest集成与断言体系

本节摘要:把用例交给测试运行器托管,自动化才算从"脚本"升级为"套件":统一的用例发现与命名、断言与报告、参数化与标记、以及最关键的——浏览器生命周期由夹具统一托管。本节以 pytest 为例讲清这套集成:裸脚本的三大症状、夹具的托管设计、参数化与标记的日常用法。这是第 4 章其余三节的地基,页面对象和行为驱动都要跑在运行器之上。

祖传脚本的三种死相

接手过别人自动化仓库的人,多半见过这样的目录:一百多个独立脚本文件,每个都自带"启动浏览器、执行步骤、print 结果、退出"的完整流程。这种组织方式有三种死相。其一是没人知道今天哪些过了哪些挂了——没有统一执行入口,跑一遍要挨个点;其二是断言靠 print 加肉眼,回归报告无从谈起;其三是浏览器启停代码复制了一百多遍,改一个启动参数要动一百多个文件。

三种死相的病根是同一个:没有把"用例本身"与"用例的运行环境"分开。测试运行器(本节用 Python 生态最主流的 pytest)的职责正是接管后者——用例发现、执行调度、断言统计、报告产出、前后置管理。你只需要把用例写成它约定的形状,其余的它全包。

夹具:浏览器生命周期的唯一托管人

pytest 的夹具(fixture)是这场集成的核心机制:声明一个函数,标注哪些用例需要它,运行器自动在用例前执行前置、用例后执行清理。浏览器的启停正是最典型的前后置资源:

import pytest from selenium import webdriver @pytest.fixture def driver(): options = webdriver.ChromeOptions() options.add_argument("--headless=new") options.add_argument("--window-size=1920,1080") driver = webdriver.Chrome(options=options) yield driver # yield之前是前置,之后是清理 driver.quit() def test_login_page_opens(driver): driver.get("https://shop.example.com/login") assert "登录" in driver.title

这段代码值得逐行品味。yield 把函数劈成两半:前半是每条用例前的启动,后半是每条用例后的销毁——任何一条用例崩溃,清理代码照样执行,进程残留问题从机制上消失。用例函数的参数 driver 不是普通参数,是运行器按名字匹配注入的夹具——这就是依赖注入:用例声明"我要一个浏览器",不关心它怎么来。于是第 2 章冒烟脚本里那套选项配置,全仓只剩这一处;改无头模式、换浏览器,动一个函数即可。

夹具还有作用域维度:默认每条用例都新建浏览器(隔离最好、速度最慢);把作用域调到模块级或会话级,一组用例共享一个浏览器(速度快、但要自己保证用例间不互相污染)。取舍原则只有一条:先按用例级隔离起步,套件慢到不可忍受时,再对确无副作用的只读用例组放宽作用域——省时间永远排在保正确之后。

这张图是夹具托管的完整闭环,注意失败分支:清理之前多了一步"记录现场"。现在只用注释占位,第 7 章会把它实现成自动截图与日志归档——夹具层就是失败取证的天然挂载点,因为全仓只有它知道每条用例的生与死。

断言:让失败自己说话

运行器带来的第二个红利是断言体系。pytest 的断言就是普通 assert,配合运算符重写机制,失败时会自动展开两侧的实际值:

def test_cart_count(driver): driver.get("https://shop.example.com") driver.find_element("css selector", "[data-testid='add-cart']").click() badge = driver.find_element("css selector", "[data-testid='cart-badge']") assert badge.text == "1", f"加购后角标应为1,实际是{badge.text!r}"

写断言的功夫在三处。第一,断言用户可感知的结果——角标文本、页面跳转、提示文案,而不是 DOM 细节;断言内部实现的用例改版即碎。第二,一条用例一个验证点,失败信息才指向明确;一股脑断十个东西的用例,挂了还要人肉排查是哪个断言挂的。第三,失败信息自己写清楚期望与实际,上面示例的 f-string 就是模板——三个月后凌晨看报告的人,会感谢现在多打的那行字。

参数化与标记:一份逻辑,多种输入

数据驱动思想的最小实现是参数化:同一套步骤,喂多组数据,每组独立执行、独立报告:

@pytest.mark.parametrize("keyword, expect_min_rows", [ ("机械键盘", 1), ("不存在的商品xyz", 0), ]) def test_search(driver, keyword, expect_min_rows): driver.get("https://shop.example.com") driver.find_element("id", "search-box").send_keys(keyword) driver.find_element("id", "search-btn").click() rows = driver.find_elements("css selector", "[data-testid='goods-row']") assert len(rows) >= expect_min_rows

标记(mark)则是用例的元数据:@pytest.mark.smoke 标冒烟集,执行时按标记过滤;skipskipif 表达"这条暂时不跑及为什么";xfail 表达"已知缺陷,挂了才算对"。标记的本质是把执行策略写进用例本身,流水线上的分层执行(冒烟先行、全量殿后)全靠它支撑,第 7 章 CI 集成时是关键道具。

与后续三节的接口

运行器接管了"怎么跑",但用例里仍满是定位器与页面细节——那是 4.2 页面对象要收编的;夹具管住了浏览器,但用例数据还写死在代码里——那是 4.4 要外置的;至于用例的可读性还能不能更进一步、让产品经理也能看懂,4.3 的行为驱动给出另一种答案。按顺序读,因为页面对象的测试写法就长在 pytest 的结构上。

收工清单

  • 运行器管环境,你只管用例:发现、调度、断言统计、报告,四件事全部外包。
  • 夹具即托管:yield 前置后置天然成对,失败也清理;全仓浏览器配置只此一处。
  • 作用域取舍:默认用例级隔离,只读用例组才放宽;正确性永远排在速度前。
  • 断言三功夫:断用户可感知的结果、一条一个验证点、失败信息写明期望与实际。
  • 参数化与标记:一份逻辑多组数据、执行策略写进元数据,为流水线分层埋好伏笔。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U