附录 · pytest 速查与 TDD 节律清单


附录 · pytest 速查与 TDD 节律清单

附录定位:写到手生时翻的口袋卡。命令、断言、夹具、替身、节律五张表,配合正文章节的完整演练使用;每张表都标注了对应的章号,忘了来龙去脉就回去重读那一节。

一、pytest 命令速查(详见第 6 章)

场景 命令 说明
跑全部测试 pytest 按目录递归收集 test_ 开头的文件
跑单个文件 pytest tests/test_cart.py 定位到具体文件
跑单个用例 pytest tests/test_cart.py::test_empty_cart_total_is_zero 双冒号接函数名
关键字过滤 pytest -k "discount and not coupon" 表达式匹配用例名
只看上次失败 pytest --lf 复跑上轮失败的用例
失败即停 pytest -x 第一个失败就中止
显示打印内容 pytest -s 等价于 capture=no
静默进度 pytest -q 只输出点与摘要
显示用例名逐条跑 pytest -v 冗长模式,CI 日志常用
生成覆盖率 pytest --cov=src --cov-report=term-missing 需要 pytest-cov 插件
查看最慢用例 pytest --durations=10 排出耗时前十名
随机顺序 pytest --randomly-seed=last pytest-randomly 插件提供,可复现上次顺序

命令行里最值钱的组合是 pytest -x --lf:先跑上次失败的那批,一红就停。改代码的间隙反复用它,反馈周期可以压到秒级——这正是红绿重构循环对运行器的要求。

二、常用断言与异常写法(详见第 1、3 章)

pytest 的断言就是普通 assert,失败时它会做内省重写,把表达式各部分的实际值打印出来,不需要记忆 xUnit 风格的 assertEqual 家族:

import pytest def test_assertion_zoo(): total = 0.1 + 0.2 assert total == pytest.approx(0.3) # 浮点比较必须用 approx assert "优惠" in cart.summary() # 包含判断直接用 in assert cart.items == [] # 集合相等直接比 assert user.age >= 18 # 比较运算符原生支持 def test_raises(): with pytest.raises(ValueError, match="金额不能为负"): checkout.pay(-5) # match 用正则校验消息 def test_warns(): with pytest.warns(DeprecationWarning): legacy.render() # 断言警告类型 ​

浮点断言是新手事故高发区:assert 0.1 + 0.2 == 0.3 在二进制浮点世界里必挂。金额计算建议全程用整数分或 decimal.Decimal,测试里照样用 approx 兜底。

三、fixture 与参数化速查(详见第 3 章)

import pytest @pytest.fixture def cart(): """每个用例拿到全新购物车,互不污染。""" return Cart() @pytest.fixture(scope="module") def price_table(): """加载一次、多处共用的只读数据,模块内只装配回。""" return load_price_table() @pytest.fixture(autouse=True) def clean_tmp(tmp_path): """自动生效:每条用例跑完清现场。""" yield shutil.rmtree(tmp_path, ignore_errors=True) @pytest.mark.parametrize("password,level", [ ("short", 0), ("alllowercase1", 1), ("Str0ngPass", 3), ]) def test_password_level(password, level): assert rate_password(password) == level ​

记住三条就够:fixture 是带名字的"原料供应",用例把它当参数写就自动注入;yield 之前是装配、之后是拆卸;scope 只在确实昂贵的资源上放大,默认函数级最安全。参数化用来表达"同一行为、多组输入",别拿它凑用例数量。

四、测试替身速查(详见第 3 章)

from unittest.mock import Mock, patch, call def test_checkout_calls_gateway(mocker): gateway = mocker.Mock(return_value={"status": "ok"}) checkout = Checkout(gateway=gateway) checkout.pay(order) gateway.charge.assert_called_once_with(amount=order.total) def test_retry_on_timeout(mocker): gateway = mocker.Mock(side_effect=[TimeoutError, {"status": "ok"}]) assert checkout.pay(order).status == "ok" assert gateway.charge.call_count == 2 ​

替身家族一句话区分:dummy 占位不参与行为,stub 喂固定答案,spy 记录调用轨迹,mock 预设期望再校验,fake 是能跑的简化实现(内存数据库是典型)。边界判断口诀:替身只替"你拥有协议的东西"——自己写的接口、自己定的存储抽象;第三方 SDK 与未实现的模块优先包适配层再替。

五、TDD 节律速查(详见第 1、2 章)

阶段 动作 时长上限 退出条件
红 写一条会失败的测试,跑一遍亲眼看它失败 分钟级 失败信息与预期一致
绿 写最小实现,只求通过当前测试 分钟级 全部测试转绿
重构 消除重复、改命名、提函数 分钟级 测试保持绿,代码更好读
提交 小步提交,留下循环轨迹 每轮次 仓库处于可发布状态

三定律贴在显示器边上:在没有失败的测试之前,不写产品代码;只写刚好失败的测试,别超前;只写刚好让测试通过的代码,别顺手发挥。红灯超过半小时就说明测试切得太大,退回去拆。

六、常见信号与对策索引

信号 多半说明 去哪章看对策
断言越写越像实现本身 测试耦合了内部结构 第 3、4 章
mock 满天飞、改接口要改十处测试 替身越界替了不该替的 第 3 章
覆盖率很高、线上照样出事 度量错了对象 第 7 章
循环一转就是半天 测试切太大或环境太重 第 2、5 章
团队觉得"没时间写测试" 反馈闭环没建立 第 7 章

这张附录刻意做薄:速查卡的价值在"翻得快",不在"讲得全"。每个条目背后的取舍与陷阱都在正文的对应章节里展开过——查表是起点,重读才是归宿。

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