3.2 fixture 与测试数据:构造、清理与供给


3.2 fixture 与测试数据:构造、清理与供给

本节摘要:fixture 是 pytest 给"测试前提"起的正式名字——带名字的原料供应,负责在每个用例前把现场搭好、用完把现场拆掉。本节讲透夹具的解剖结构:yield 分界装配与拆卸、scope 决定共享半径、conftest 负责跨文件共享,以及工厂函数与参数化在数据供给上的分工。读完你应能把上一节"独立"柱子的要求变成不假思索的机械动作。

小林在共享购物车上又翻了一次车:他给折扣测试建了个模块级 cart 变量,上午还绿着,中午加的新用例往同一辆车里塞了件商品,下午整批一跑,折扣测试全红。老周看完只说了一句:"前提要么自己造,要么交给夹具——反正不能躺在模块上等人来共享。"这句话里的"交给夹具",就是本节的主角。

从一次翻车说起:前提需要管理者

上一节的"独立"柱子要求每条测试自带前提、用完自清。手工满足这个要求很快就会遇到麻烦:二十条测试都要"一辆装了三件商品的购物车",把构造代码复制二十遍是重复;抽成一个公共变量又回到小林的翻车现场——共享的是实例,不是构造方法。pytest 的 fixture 解决的正是这个矛盾:共享构造过程,不共享实例。每个用例声明自己需要什么原料,pytest 在用例开始前现场装配一份全新的,用完按需拆卸。

import pytest from decimal import Decimal from shop.cart import Cart @pytest.fixture def cart(): """全新的空车——每个用例各得一份,互不知道对方存在。""" return Cart() @pytest.fixture def cart_with_three_items(cart): """夹具可以消费夹具:装配过程同样能复用。""" cart.add(sku="BOOK", price=Decimal("30.00"), qty=1) cart.add(sku="PEN", price=Decimal("3.50"), qty=2) return cart def test_total_is_sum(cart_with_three_items): assert cart_with_three_items.total() == Decimal("37.00")

用例把夹具名写成参数,pytest 自动注入。依赖链也顺理成章:cart_with_three_items 声明要 cart,pytest 递归装配。测试意图反而更清楚了——用例签名直接回答"这条测试的前提是什么"。

夹具的解剖:yield 之前装配,之后拆卸

拆卸需求在碰到文件、临时目录、数据库连接时立刻出现。pytest 的写法是在夹具里用 yield 分界:之前是装配,之后是拆卸,异常时拆卸照样执行。

@pytest.fixture def temp_ledger(tmp_path): ledger = Ledger(db_path=tmp_path / "ledger.sqlite") # 装配 ledger.connect() yield ledger # 用例在此运行 ledger.close() # 拆卸,异常也执行

顺带注意 tmp_path:pytest 内置的每用例临时目录,用完自动清理。内置夹具还有管理补丁的 monkeypatch(第 5 章治时间随机时重度使用)和捕获输出的 capsys——先混个脸熟,用得到时它们都在。

图:fixture 生命周期图

图:fixture 生命周期图

测试数据:工厂、参数化与真实感

夹具解决"构造",数据本身还有一讲究。三条经验排个序:

**少量固定值优先。**测试数据要的是可解释,不是逼真。"BOOK" 比"诺贝尔文学奖得主观感"好——失败信息里看得懂。重复构造用工厂函数,默认值兜底、参数覆盖个别字段:

def make_item(**overrides): defaults = dict(sku="SKU-1", price=Decimal("10.00"), qty=1) defaults.update(overrides) return Item(**defaults) def test_zero_price_item_is_rejected(): cart = Cart() cart.add(make_item(price=Decimal("0"))) assert cart.total() == Decimal("0")

同输入不同期望交给参数化(2.4 的规矩),工厂与参数化是互补而非竞争。至于随机数据——测试要确定性,随机种子必须固定或干脆别用;想让"奇怪输入"参与测试,交给属性测试工具或显式边界清单,不交给掷骰子。

夹具与参数化还能组合出"装配矩阵":同一套前提,按参数换不同装配结果,一次声明覆盖多种现场。常见于"同一规则在不同会员等级下"这类场景——夹具管装配的形状,参数管场景的枚举,用例本体保持一条。组合的度在于可读性:一旦夹具签名膨胀到看不懂,说明装配复杂度该退回工厂函数里去,别硬塞在夹具参数上。

⚠️ 夹具最常见的滥用是把"步骤"当"原料":一个夹具里连做注册、下单、支付三件事,用例根本看不出自己在哪一步开始。原料要细粒度,剧情留给用例自己演。

💡 判断夹具设计好坏的一条速测:把任意一条用例单独拎出来跑,它需要的全部上下文都应从夹具签名读出来。签名读不全的,就是有隐藏前提。

夹具守则

  • 共享构造不共享实例,默认 function 级,别为省那几毫秒赌上独立性。
  • yield 前装配后拆卸,拆卸写进夹具而不是用例,异常路径也干净。
  • conftest 就近安家,夹具跟着使用者走,不做中央仓库。
  • 数据要可解释,工厂管重复,参数化管矩阵,随机不进主路径。
  • 夹具签名即前提文档:看不懂签名的前提,就是隐藏前提。

最后一条守则呼应本章的立场:夹具技术不难,难的是把"测试现场自管"当成默认审美。当团队里新写的测试自动长成"夹具供给、用例干净"的形状时,3.1 那四根柱子就不再需要逐条检查——它们已经长在土里了。现场管好了,还差最后一块骨架:外部依赖怎么挡在门外——下一节替身登场。


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