这是一份从入门到深入的 Pytest 面试笔记,共 72 道题,覆盖基础使用、fixture、参数化、Mock、插件机制、并行与异步,以及接口自动化和 CI 的实际项目场景。
热度说明: 以下星级是结合测试开发、自动化测试岗位常见考点做的经验判断,不是招聘平台统计或实时流行度排名,不同公司和职级会有差异。
| 标记 | 经验热度 | 复习建议 |
|---|---|---|
| ★★★★★ | 高频核心 | 应能脱稿解释,并现场写基本代码 |
| ★★★★☆ | 常见追问 | 应能解释设计取舍和常见故障 |
| ★★★☆☆ | 进阶区分项 | 适合中高级测试开发岗位 |
| ★★☆☆☆ | 专项深入 | 按项目经验和岗位要求准备 |
阅读顺序: 第一轮掌握所有五星题;第二轮补齐四星题;第三轮练习插件机制、异步、并行和项目设计题。回答建议遵循“结论 → 原理 → 例子 → 边界与取舍”。
版本与示例约定: 核心内容采用 Pytest 常用稳定 API,整理时参考官方 stable 文档。插件行为会随版本变化,项目应固定 Python、Pytest 和插件版本。普通代码块是独立示例;标明文件名的多文件示例要按目录配套使用。项目场景中的业务客户端、数据库封装等属于设计说明,不代表 Pytest 自带能力。
一、入门:发现、执行与断言(1—10)
1. Pytest 是什么?为什么在自动化测试中使用它?
热度:★★★★★|难度:入门
参考答案: Pytest 是 Python 测试框架,负责发现测试、组织依赖、运行用例和汇总结果。它既能执行函数级单元测试,也能组织接口、数据库、浏览器等集成测试。
核心优势是普通函数即可成为测试用例,assert 失败时能显示表达式信息;fixture 可以复用准备与清理逻辑;参数化可以让同一测试逻辑覆盖多组输入;插件生态可以扩展报告、并行和覆盖率。
面试加分点: Pytest 本身不是 HTTP 客户端、浏览器驱动或压测工具。接口测试通常还需要 requests/httpx,浏览器测试需要相应驱动,Pytest 负责测试组织与执行。不要把项目中用到的所有能力都说成 Pytest 内置。
2. Pytest 与 unittest 有什么区别?能混用吗?
热度:★★★★★|难度:入门
| 对比项 | Pytest | unittest |
|---|---|---|
| 来源 | 通常额外安装 | Python 标准库 |
| 用例组织 | 测试函数、普通测试类 | 通常继承 TestCase |
| 断言 | 普通 assert |
assertEqual 等断言方法 |
| 前后置 | fixture,兼容部分 xunit 风格 | setUp、tearDown 等 |
| 数据驱动 | 内置参数化 | 可用 subTest 或其他方案 |
参考答案: Pytest 能运行大多数 unittest 测试,因此迁移时可以先换执行器,再逐步改用 fixture 和参数化,不必一次重写。
常见追问: unittest.TestCase 测试方法不能像普通 Pytest 函数那样直接接收 fixture 参数,也不能直接使用 Pytest 参数化机制。部分 fixture 能通过 autouse 或 usefixtures 做环境准备,但不能由此推断两种测试模型完全一致。
3. Pytest 默认如何发现用例?为什么有些测试没有被收集?
热度:★★★★★|难度:入门
参考答案: 对普通 Python 测试,默认收集 test_*.py、*_test.py 文件;收集 test 开头的测试函数,以及 Test 开头且没有自定义 __init__ 的测试类中的测试方法。测试类通常不需要继承某个框架基类。
|
|
未收集时检查文件名、函数名、类名、执行路径、testpaths、忽略规则,以及模块导入是否报错。先运行 python -m pytest --collect-only -q,把“没发现”和“发现后执行失败”区分开。
易错点: 测试类中写 __init__ 初始化数据可能导致不被正常收集,推荐用 fixture。参见测试发现与项目组织。
4. 常用执行命令有哪些?-k 和 -m 的区别是什么?
热度:★★★★★|难度:入门
|
|
参考答案: -k 根据名称和相关关键字筛选,-m 根据标记筛选。团队应使用 marker 表达测试分类,用测试项 ID 精确定位失败;只用模糊名称筛选容易选错或漏选。
追问: --lf 依赖本地缓存,不能替代一次完整回归,也不能假设全新 CI 环境拥有上次执行记录。参见执行命令。
5. pytest 与 python -m pytest 有什么区别?rootdir 会修复导入吗?
热度:★★★★☆|难度:基础
参考答案: python -m pytest 使用指定 Python 解释器中的 Pytest,适合避免虚拟环境不一致;还会遵循 python -m 的行为,将当前目录加入 sys.path。直接执行 pytest 时,要确认命令属于哪个环境。
rootdir 用于确定配置、测试项 ID 等上下文,不会自动把根目录加入 sys.path。导入失败时,应检查项目包是否安装、目录布局和导入模式,不能仅靠设置 --rootdir 解决。
项目建议: 使用正确的打包配置,在虚拟环境中安装项目,必要时采用 src/ 布局;减少到处修改 sys.path 的临时方案。参见导入机制。
6. Pytest 为什么能展示详细断言信息?辅助模块也一样吗?
热度:★★★★☆|难度:进阶
参考答案: Pytest 会对符合条件的测试模块进行断言重写,在导入执行前处理断言表达式,因此失败时可以展示左右值等信息。它不是简单地捕获 AssertionError 后猜测表达式。
辅助断言模块不一定自动获得相同的重写行为。如果插件或共享包需要重写,应在目标模块首次导入前调用 pytest.register_assert_rewrite("package.assertions")。
|
|
易错点: 不要先导入辅助模块,再注册重写;也不要把生产代码中不可省略的参数校验建立在普通 assert 上。参见断言与重写。
7. 浮点数、集合和无序结果应如何断言?
热度:★★★★☆|难度:基础
参考答案: 浮点数使用 pytest.approx 设置容差;无序数据根据业务语义选择集合、排序或 Counter,不能一律转换成集合。
|
|
集合会忽略重复次数。如果接口返回的条目不能重复,转成集合后可能掩盖缺陷;Counter 更适合顺序无关但次数有意义的结果。金额规则优先使用明确的最小货币单位或 Decimal,容差不能替代业务精度设计。
8. 如何验证异常?raises 和 xfail 能互相替代吗?
热度:★★★★★|难度:基础
|
|
参考答案: pytest.raises 表示“抛出异常就是正确行为”,满足条件时测试通过;xfail 表示“当前测试预计不能满足正常预期”。两者语义不同。
match 是正则匹配;如果需要匹配用户输入中的特殊符号,应考虑 re.escape。raises 默认接受指定异常的子类,只有业务要求精确类型时才额外比较 exc_info.type。
易错点: 将过多准备逻辑放进 raises 块,可能把准备阶段的异常误判为目标异常;异常抛出后的同一块内代码也不会继续执行。
9. 测试有哪些阶段?FAILED 和 ERROR 如何区分?
热度:★★★★★|难度:基础
参考答案: 一个正常运行的测试项通常经历 setup → call → teardown。setup 准备 fixture 等环境,call 执行测试函数,teardown 清理已准备的资源。
测试函数中的断言或其他未处理异常通常显示为 FAILED;fixture 初始化或清理阶段出错通常显示为 ERROR。一个测试的调用阶段失败,清理阶段也可能再次报错,因此不能只记录单一“最终布尔值”。
追问: 若 setup 失败,call 通常不执行,但已经成功准备的 fixture 仍需清理。收集阶段的导入错误属于更早的问题,不能误认为测试函数已经运行。
10. 如何调试失败?退出码有哪些?
热度:★★★★☆|难度:基础
参考答案: 先使用测试项 ID 单独复现,再查看回溯、输入数据、日志和环境。可用 --pdb 在失败时进入调试器,--trace 在测试开始处进入调试;并行调试时先切回单进程,减少干扰。
| 退出码 | 含义 |
|---|---|
| 0 | 测试执行成功 |
| 1 | 存在测试失败 |
| 2 | 执行被中断 |
| 3 | Pytest 内部错误 |
| 4 | 命令行使用错误 |
| 5 | 没有收集到测试 |
CI 应检查退出码,不能只判断有没有报告文件。退出码 5 需要结合任务意图处理,不能无条件变成成功。参见退出码定义。
二、核心:fixture 与资源生命周期(11—24)
11. fixture 是什么?为什么不直接调用工具函数?
热度:★★★★★|难度:基础
参考答案: fixture 提供测试依赖,并由框架管理创建、复用和释放。测试函数通过参数名称声明需求,Pytest 按依赖关系准备对象。
|
|
普通工具函数适合纯计算、格式转换等显式调用逻辑;fixture 适合客户端、账号、数据库连接等有生命周期的依赖。不要把所有工具函数都包装成 fixture,否则依赖图会难以理解。
追问: fixture 的名称是依赖解析入口,不应在测试中直接调用被装饰的 fixture 函数。需要复用其业务逻辑时,将逻辑拆到普通函数,再由 fixture 调用。
12. fixture 有哪些 scope?扩大 scope 一定更好吗?
热度:★★★★★|难度:基础
| scope | 通常的共享范围 |
|---|---|
function |
每个测试调用,默认值 |
class |
测试类 |
module |
测试模块 |
package |
测试包 |
session |
当前测试会话 |
参考答案: scope 决定缓存和生命周期边界。低作用域隔离性好,高作用域减少重复初始化成本。选择时同时考虑资源价格、可变状态和并发安全。
例如 HTTP 连接池可能适合会话复用,而订单、购物车和可变用户资料一般需要每个用例独立创建。参数化 fixture 即使在同一 scope 内也可能发生多次初始化,不能简化为“session 就一定只创建一个对象”。参见fixture 使用指南。
13. fixture 为什么按名称注入?一次测试中请求两次会创建两个对象吗?
热度:★★★★☆|难度:进阶
参考答案: Pytest 根据参数名称解析 fixture,并缓存当前有效实例。一个测试函数和它依赖的另一个 fixture 同时请求同一个 fixture,通常会拿到同一次创建的对象,而不是各创建一份。
|
|
易错点: 同一用例内共享是依赖机制的一部分;跨用例是否共享取决于作用域和参数化。需要同一用例创建多个独立对象时,应使用工厂 fixture,而不是误以为重复声明依赖就会重新创建。
14. fixture 执行顺序由什么决定?
热度:★★★★★|难度:进阶
参考答案: 主要看作用域、显式依赖和 autouse。高作用域资源先准备;依赖项先于依赖它的 fixture;同一作用域中适用的自动 fixture 会优先执行,其依赖也随之提前。
假设 client → token → account,准备顺序为 account → token → client,正常清理时按相反方向释放。若两个 fixture 之间没有约束,不能把当前观察到的排列当成稳定接口。
面试陷阱: 函数定义位置、fixture 名字和测试参数的左右顺序,都不应承担强制执行顺序的职责。如果 B 必须等 A,直接让 B 声明依赖 A。参见fixture 可见性与执行顺序。
15. return、yield 和 request.addfinalizer 如何选择?
热度:★★★★★|难度:进阶
参考答案: return 提供值;yield 在提供值的同时,把清理写在后半段;addfinalizer 可在运行过程中动态登记清理回调。
|
|
yield 的后置代码只有在执行到 yield 后才会进入清理流程;已登记的 finalizer 即使后面的 fixture 初始化失败,仍会被调用。通常优先选择清晰的 yield fixture;动态多资源场景可用 finalizer 或 ExitStack。
易错点: 先登记清理、后创建资源,可能在资源根本不存在时运行错误的清理。登记时机和清理幂等性都要设计。
16. fixture 在 yield 前失败,如何避免部分资源泄漏?
热度:★★★★☆|难度:进阶
参考答案: 将多个资源拆为彼此依赖的小 fixture,或每创建成功一个资源就登记对应清理。不要把“创建账号、开连接、启动任务”全部堆在一个 yield 前面,再假设末尾清理永远会执行。
例如账号 fixture 创建成功并 yield,客户端 fixture 再依赖账号。即使客户端初始化失败,账号 fixture 已进入可清理状态,仍能删除账号。
项目边界: 进程被强杀、机器断电等情况下,Python 清理逻辑可能无法执行。对外部资源还要有测试运行标签、过期时间和定期回收机制。框架内清理保证不等于跨故障的绝对保证。
17. conftest.py 的作用和可见性是什么?
热度:★★★★★|难度:基础
参考答案: conftest.py 是目录范围的本地插件入口,可定义 fixture 和 hook,Pytest 自动发现并加载。测试通常无需手动导入。
|
|
orders 下的测试可以使用上层 fixture,并通过更近的同名定义覆盖它;其他兄弟目录不会因为文件存在,就自动获得 orders 内的 fixture。
追问: 根目录放全项目通用资源,业务目录放局部资源。避免巨型 conftest.py,也不要把它当普通工具模块相互导入,否则容易产生导入歧义和循环依赖。
18. autouse=True、usefixtures 和参数注入的区别?
热度:★★★★★|难度:基础
参考答案: 参数注入既声明依赖又拿到返回值;usefixtures 显式要求某测试或测试类执行 fixture,但不提供变量;autouse=True 在可见且适用的范围自动执行。
例如统一设置时区、禁止真实外部请求,可以考虑自动 fixture;订单数据、登录身份等业务依赖更适合显式声明,让读者一眼看出用例需要什么。
易错点: usefixtures 不会将返回值放进测试的局部变量;在 fixture 函数上写 usefixtures 也不能替代真正的参数依赖。自动 fixture 不是“整个仓库无条件执行”,它仍受可见性和作用域影响。
19. 为什么出现 ScopeMismatch?怎么改?
热度:★★★★☆|难度:进阶
参考答案: 长生命周期 fixture 请求了短生命周期 fixture。例如 session 资源依赖 function 资源,但后者每个测试变化一次,无法成为整个会话稳定的依赖。
处理方式有两类:缩短请求方的生命周期;或者拆分高作用域的基础资源与低作用域的业务状态。例如 session 数据库引擎提供连接能力,function 事务或测试账号负责用例隔离。
易错点: 为了消除异常,把所有 fixture 都改成 session,可能把一个明显的生命周期错误变成难排查的数据污染。依赖方向应该与资源寿命一致。
20. 什么是工厂 fixture?适合什么场景?
热度:★★★★☆|难度:进阶
参考答案: fixture 返回一个可调用对象,测试可以按需多次调用,创建不同参数的资源。适合同一用例需要多个账号、订单或文件,并希望集中清理。
|
|
追问: 资源创建成功后再加入清理清单;删除按逆序进行,适合存在父子依赖的资源。外部清理还需处理部分删除失败。
21. fixture 能参数化吗?与测试参数化有什么区别?
热度:★★★★☆|难度:进阶
参考答案: fixture 可通过 params 生成多种依赖配置,值由 request.param 获取;所有请求它的测试都会覆盖这些配置。测试参数化则更适合某一个测试的输入输出组合。
|
|
数据库类型、序列化协议等多用例共同维度适合 fixture 参数化;单个校验函数的边界值适合测试参数化。两者组合可能放大用例数,设计前先估算执行成本。
22. 如何覆盖上层 fixture?动态获取 fixture 有什么风险?
热度:★★★☆☆|难度:进阶
参考答案: 在更近的测试模块或目录 conftest.py 中定义同名 fixture,可以覆盖上层定义。扩展上层同名 fixture 时,可以通过参数请求上层版本,再附加局部配置。
request.getfixturevalue(name) 能按运行时名称获取 fixture,适合依赖名确实由运行条件决定的场景。但会隐藏静态依赖、增加定位难度,也不能突破 scope 约束。
项目建议: 优先使用显式参数依赖;覆盖时保持语义一致,不要在某个目录把“只读客户端”同名替换成有写入副作用的对象,破坏调用方预期。
23. fixture 与 setup_method、teardown_method 怎么选?
热度:★★★★☆|难度:基础
参考答案: Pytest 支持 xunit 风格的前后置方法,简单类内准备可以使用;fixture 更适合跨模块复用、按依赖组合、控制 scope 和参数化。
例如多个测试类都需要账号、客户端和订单时,用 fixture 可以只声明当前测试所需的资源,不必依赖深层继承。类属性式共享状态则更容易形成隐性耦合。
易错点: 不要在两个体系中重复创建和删除同一资源。渐进迁移时先明确每个资源归谁管理,再替换前后置机制,防止出现双重清理或先后顺序假设。
24. 如何查看 fixture 来源、作用域和实际准备顺序?
热度:★★★☆☆|难度:进阶
|
|
参考答案: --fixtures 查看可用 fixture,--fixtures-per-test 看某测试依赖,--setup-show 随执行显示准备清理,--setup-plan 展示计划而不实际运行测试和 fixture。
定位问题时优先确认同名 fixture 是否被局部覆盖,再看参数化和 scope。盲目加 print 容易被输出捕获干扰,也难以还原完整依赖关系。
三、熟练使用:参数化、标记和配置(25—34)
25. 如何使用 parametrize?为什么比循环断言更适合数据驱动?
热度:★★★★★|难度:基础
|
|
参考答案: 每组数据生成独立测试项,可以独立统计、定位和重跑。普通循环中,第一条断言失败通常就终止整个测试,后续数据没有执行,报告粒度也更粗。
ids 应使用短而稳定的业务描述,不要放密码、token 或个人信息,因为 ID 会出现在日志和报告中。对于连续业务步骤,则不一定应拆成独立参数项,要看每项是否能独立准备环境。
26. 多个参数化装饰器如何组合?如何控制组合爆炸?
热度:★★★★☆|难度:进阶
参考答案: 叠加参数化通常生成笛卡尔积,例如 3 个地区、4 种角色和 5 种支付方式会得到 60 组测试;若再叠加 fixture 参数化,数量继续增长。
全组合适合维度少且交互风险高的情况。实际项目可以按业务约束剔除无效组合,对核心路径做完整组合,其余采用代表值、边界值或成对组合策略,并说明剩余风险。
易错点: 用例数量多不等于覆盖有效。大量永远被 skip 的组合既拖慢收集,又让报告难以阅读。参见参数化行为与组合。
27. 什么是间接参数化?indirect=True 到底改变了什么?
热度:★★★★★|难度:进阶
|
|
参考答案: 普通参数化把值直接传给测试;间接参数化先把值传给同名 fixture 的 request.param,再把 fixture 结果注入测试。这里测试拿到字典,fixture 拿到角色字符串。
它适合把轻量配置转换为昂贵资源,初始化在 setup 阶段进行。indirect=True 表示相关参数都走 fixture;只需要部分参数时用名称列表。
常见错误: 误把 fixture 对象本身放进参数列表,期待框架自动解析;或者忘记定义对应 fixture。
28. pytest.param 能做什么?可变参数有什么坑?
热度:★★★★☆|难度:进阶
参考答案: pytest.param 可以给单组数据设置 ID 和标记,例如只给已知缺陷对应的数据加 xfail,而不是把整组用例都标记为预期失败。
|
|
参数中的列表、字典不会自动深拷贝。如果不同测试项引用同一个可变对象,并在运行时修改它,可能造成相互污染。可以使用不可变输入,或在 fixture 中依据配置重新构建对象;不要把运行结果写回原始测试数据。
29. 用例数据放在 YAML、JSON、CSV 还是代码里?
热度:★★★★☆|难度:项目应用
参考答案: 小型输入输出表直接写在代码里更直观,也便于代码审查;跨工具共享的结构化数据可以选择 JSON/YAML;扁平大量数据可选择 CSV。选择标准是可读性、类型表达和维护成本,不是“文件越多越像框架”。
外部数据要进行结构校验、稳定排序和唯一 ID 校验;文件路径应基于模块或明确配置定位,不能依赖偶然的工作目录。读取 YAML 时使用安全加载方式。
追问: 把逻辑表达式、循环和任意代码执行都塞进 YAML,会形成一套难调试的自制语言。数据文件负责输入与预期,业务行为留在可测试的代码中。
30. skip、skipif、xfail、XPASS 有什么区别?
热度:★★★★★|难度:基础
| 机制 | 含义 | 典型使用 |
|---|---|---|
skip |
不运行该测试 | 当前暂不适用 |
skipif |
满足条件时跳过 | 系统或依赖条件不满足 |
xfail |
正常执行但预计失败 | 已知缺陷,仍需跟踪 |
| XPASS | 预期失败的测试意外通过 | 缺陷可能已修复或预期失效 |
参考答案: xfail 通常仍执行测试,strict=True 会把意外通过变成失败,促使团队更新状态;特殊情况下可用 run=False 禁止执行,但需说明原因。
|
|
项目要求: 跳过和预期失败应附原因、缺陷编号或到期条件,不应成为长期掩盖失败的入口。参见跳过与预期失败。
31. 如何注册 marker?为什么建议严格校验?
热度:★★★★★|难度:基础
|
|
参考答案: 自定义 marker 是测试分类和附加元数据,例如冒烟、集成或慢测试。注册后有统一说明,--strict-markers 能把拼写错误等未注册标记转成错误,防止筛选无声失效。
@pytest.mark.smoke 本身不会改变业务逻辑,实际选择由 -m smoke 或团队 hook 决定。不要把 marker 当权限控制机制,危险环境还需要执行前的硬校验。
32. 配置文件如何选择?多个文件会合并吗?
热度:★★★★☆|难度:进阶
参考答案: 常见配置入口包括 pytest.ini 和 pyproject.toml;配置格式与发现优先级要以项目固定版本为准。团队应选定一个权威入口,避免同目录多份配置相互遮蔽。
Pytest 的配置发现不是把找到的所有文件逐层合并。运行时应查看启动头部的 rootdir、配置文件和插件,必要时用 -c 指定配置。
例如 pyproject.toml 的常用兼容写法是 [tool.pytest.ini_options];pytest.ini 则是 [pytest],不能直接复制节名。执行选项还可能来自环境变量和命令行,因此排查时要记录完整调用环境。参见官方配置说明。
33. 如何新增 --env 参数,并避免误连环境?
热度:★★★★☆|难度:进阶
|
|
参考答案: 用 pytest_addoption 注册命令行选项,通过配置对象或 fixture 读取;环境到地址的映射集中管理,测试用例只依赖客户端。
示例是配置演示,不向这些地址发请求。真实项目中环境参数还应受允许列表、凭据和账号权限约束;不能因为传了一个字符串,就允许任意目标写入。机密信息不放在 URL 或测试项 ID 中。
34. 动态生成测试用例如何实现?与普通参数化有什么区别?
热度:★★★☆☆|难度:深入
参考答案: 可在收集阶段使用 pytest_generate_tests(metafunc),根据测试所需参数和配置调用 metafunc.parametrize。适合多模块复用同一动态数据来源,而不是到处复制装饰器。
|
|
|
|
收集阶段应快速、确定、无业务写入。不能一边从不稳定远程数据源实时取数,一边假设并行 worker 一定生成相同测试项。昂贵的资源初始化应推迟到 fixture setup,必要时使用间接参数化。
四、深入测试设计:Mock、输出和可测性(35—44)
35. monkeypatch、unittest.mock、mocker 有什么区别?
热度:★★★★★|难度:基础
参考答案: monkeypatch 是 Pytest 内置 fixture,主要负责临时替换属性、字典项、环境变量、工作目录等,并在作用域结束后恢复;unittest.mock 是标准库,提供 Mock 对象、行为模拟和调用断言;mocker 来自额外的 pytest-mock 插件,便于管理 Mock 生命周期。
|
|
可以让 monkeypatch 安装一个 Mock 对象,由 Mock 记录交互。不要把“替换目标”和“检查调用”看成完全相同的职责。参见monkeypatch 官方指南。
36. 为什么 patch 没生效?应该 patch 哪个位置?
热度:★★★★★|难度:进阶
参考答案: 替换被测代码实际查找对象的位置,而不是机械地替换对象最初定义的位置。
|
|
|
|
|
|
service 已绑定导入的 send 名称,只替换 gateway.send 不会自动重新绑定它。若代码通过 gateway.send() 访问,则围绕该访问路径替换。
37. return_value、side_effect、spec、autospec 怎么用?
热度:★★★★☆|难度:进阶
参考答案: return_value 设置调用结果;side_effect 可表示异常、连续结果或动态计算;spec 限制模拟对象可用的属性,spec_set 还限制设置不存在的属性;autospec 能基于真实对象约束调用签名,减少“接口已经改了,Mock 仍然通过”的问题。
|
|
autospec 不能保证业务协议正确,也不能取代真实接口或契约测试。参见标准库 Mock 文档。
38. 断言 Mock 调用时常见错误有哪些?
热度:★★★★☆|难度:进阶
参考答案: assert_called_with 检查最近一次调用,不代表只调用了一次;assert_called_once_with 同时检查次数和参数。验证顺序时可比较调用列表,但不要对无关实现细节过度约束。
另一个隐蔽问题是可变参数:Mock 记录的可能是对象引用,调用后对象被修改,再检查历史参数时看到的已经不是原始内容。需要保留快照时,可以在副作用函数中复制必要字段。
项目取舍: 应验证业务上有意义的交互,例如扣款金额与幂等键,而不是把每个内部辅助函数调用次数都写死。后者会让正常重构引发大量无意义失败。
39. Mock、Stub、Fake、Spy 有什么区别?Mock 越多越好吗?
热度:★★★★☆|难度:进阶
参考答案: 常用术语中,Stub 提供预设结果,Mock 强调交互期望,Fake 是可工作的简化实现,Spy 记录或观察调用。不同库命名可能不完全一致,面试重点是说清替身的用途。
例如内存版仓库可作为 Fake,固定返回用户资料的是 Stub,验证支付网关只被正确调用一次可使用 Mock,包装真实函数观察调用可使用 Spy。
取舍: 外部网络、昂贵资源和难触发异常适合替换;核心业务逻辑应尽量真实执行。若把被测逻辑本身全部 Mock 掉,测试很可能只证明“预设返回值会被返回”。
40. 如何断言标准输出?capsys 与 capfd 有什么区别?
热度:★★★☆☆|难度:进阶
|
|
参考答案: capsys 捕获 Python 的 sys.stdout、sys.stderr;capfd 在文件描述符层捕获,适合还包含底层库或继承相关描述符的子进程输出。二进制输出有相应的 binary fixture。
-s 用于关闭普通输出捕获,适合本地观察;它不是日志断言方法,也不能保证 xdist 下每个 worker 的输出像串行一样实时展示。读取捕获结果后,应留意后续输出属于新的捕获段。参见输出捕获说明。
41. 如何使用 caplog 验证日志?
热度:★★★☆☆|难度:进阶
|
|
参考答案: 根据 logger、级别和关键字段断言,比只在整段文本中查一个词更准确。日志格式中的时间、线程号等常变化内容一般不应写死。
追问: 若被测代码重新配置 root logger 并移除了捕获 handler,caplog 可能失效;应检查日志配置,而不是先认定系统没有产生日志。测试日志也应脱敏,避免把认证头直接写进报告。
42. tmp_path 与 tmp_path_factory 如何选择?
热度:★★★★☆|难度:基础
参考答案: tmp_path 提供当前测试独立的临时目录,返回 pathlib.Path;tmp_path_factory 可以在高作用域 fixture 中创建目录,适合会话级准备昂贵静态文件。
|
|
不要让所有测试写同一个固定路径,也不要假设当前工作目录可写。Pytest 临时目录可能保留部分历史运行以便排查,因此临时目录并不意味着测试结束后立即安全擦除,敏感数据仍需专门管理。
43. 如何测试时间、随机数和超时逻辑?
热度:★★★★☆|难度:项目应用
参考答案: 优先让业务函数接收时钟、随机源或等待器,以便测试显式控制。持续时间使用单调时钟,业务日期使用带明确定义时区的时间。
测试重试逻辑时注入可控制的等待函数,验证重试上限和退避计算,无需真的等待几十秒。随机测试记录 seed 和输入,以便复现;固定 seed 也不能保证来自多个独立随机源的行为全部一致。
易错点: 只 patch 某个模块中的 datetime,不代表所有库和进程的时间都被冻结。系统时间、单调时间与事件循环时间也不是同一概念,测试应明确控制哪一个。
44. 单元测试、集成测试和端到端测试如何分工?
热度:★★★★★|难度:项目应用
参考答案: 单元测试关注局部规则和边界,尽量快速确定;集成测试验证数据库、HTTP 协议或消息系统等真实协作;端到端测试覆盖少量关键业务路径。
例如价格计算可以用大量单元参数化测试,订单持久化用真实测试数据库验证,购买链路用少量端到端用例检查系统是否协同工作。
面试加分点: 分类不应只按是否用 Pytest 或是否发 HTTP 请求决定,而要看验证边界。端到端用例通常更慢、故障面更大,因此应通过分层减少重复覆盖;同时保留真实集成测试,防止 Mock 与真实服务协议漂移。
五、框架进阶:hook、插件、并行与异步(45—54)
45. hook 与 fixture 有什么区别?常用 hook 有哪些?
热度:★★★★☆|难度:深入
参考答案: fixture 管理测试需要的依赖与资源;hook 在框架生命周期事件发生时参与处理,可影响配置、收集、报告等。Pytest 基于 pluggy 组织插件和 hook 实现。
| hook | 常见用途 |
|---|---|
pytest_addoption |
注册命令行选项 |
pytest_configure |
初始化插件配置、登记元数据 |
pytest_generate_tests |
收集时生成参数组合 |
pytest_collection_modifyitems |
调整收集后的测试项或标记 |
pytest_runtest_makereport |
处理 setup/call/teardown 阶段报告 |
pytest_sessionfinish |
会话结束时进行汇总 |
追问: 用 fixture 准备登录状态,用 hook 统一处理失败报告。不要在 hook 中隐藏业务测试前置条件,否则依赖会变得难以追踪。hook 的参数和返回值要遵循官方定义,不能任意返回自己想要的对象。
46. 如何通过 pytest_runtest_makereport 获取真实执行结果?
热度:★★★★☆|难度:深入
参考答案: 该 hook 会针对不同阶段生成报告。包装器可以先 yield 让其他实现产生结果,再读取报告,关注 when、outcome 和错误信息。
|
|
示例使用传统 hookwrapper=True 写法;新式 wrapper=True 的返回和异常处理协议不同,不能混用模板。
项目追问: 失败截图通常应在浏览器销毁前采集,保存路径要唯一;采集失败应作为附加诊断保留,不应覆盖原始业务异常。只检查 when == "call" 会漏掉 setup 和 teardown 错误,完整质量报告必须覆盖三个阶段。参见hook 编写规则。
47. 多个插件实现同一个 hook 时,顺序和返回值如何处理?
热度:★★★☆☆|难度:深入
参考答案: hook 有普通实现和包装器实现,tryfirst、trylast 可表达相对优先级,但不应将插件安装顺序当成业务契约。包装器包围普通实现,其 yield 前后处于不同执行阶段。
部分 hook 采用 firstresult 语义,拿到第一个非 None 结果后不再继续查找普通结果;另一些收集多个实现结果。具体行为必须看 hook specification。
易错点: 不该有返回值的 hook 随意返回数据,或在一般 hook 中抛异常,可能破坏整个测试会话。可恢复的诊断错误应局部处理,真正的配置错误则应尽早、清晰地暴露。
48. 如何把公共能力抽成插件?插件本身如何测试?
热度:★★★☆☆|难度:深入
参考答案: 项目内可从 conftest.py 起步;跨项目复用时封装成安装包,通过 pytest11 entry point 注册插件,并管理依赖版本。用 --trace-config 检查加载情况,避免同一插件重复注册。
插件测试不能只测内部函数,还应验证它对真实收集、执行和报告的影响。Pytest 提供 pytester 辅助创建临时测试工程并运行测试,需要先启用 pytester 插件。
|
|
测试重点: 正常通过、setup 失败、call 失败、清理失败、用户配置错误,以及需要支持的 Pytest/插件版本组合。参见插件编写与测试。
49. xdist 如何实现并行?如何选择调度策略?
热度:★★★★★|难度:进阶
参考答案: pytest-xdist 启动多个工作进程,控制进程分发测试项并汇总结果。它不是让单个测试函数自动多线程执行。安装插件后可运行 python -m pytest -n 4 或 -n auto。
| 策略 | 主要特点 | 适用取舍 |
|---|---|---|
load |
按负载分发测试项 | 通用,但可能重复资源初始化 |
loadscope |
按模块或类等作用域分组 | 减少部分 fixture 重复准备 |
loadfile |
同文件测试放到同一 worker | 适合文件级资源复用 |
loadgroup |
按 xdist_group 分组 |
控制特定测试同 worker |
worksteal |
在 worker 间调整待执行工作 | 用例耗时差异较大时可评估 |
调度策略与插件版本相关。多进程数量还应受下游服务容量约束,不能单纯追求 CPU 核数。参见xdist 调度文档。
50. xdist 下 session fixture 是否全局只执行一次?如何真正只初始化一次?
热度:★★★★★|难度:进阶
参考答案: 不是。每个 worker 都有自己的会话和进程内缓存,同一个 session fixture 可能在多个进程分别执行。普通全局变量也不能自动跨进程共享。
若初始化结果可以序列化,可在单机共享目录中使用进程锁,锁内检查结果是否存在,不存在则初始化并原子写入,其他 worker 读取。若是共享数据库或远程任务,需要更可靠的外部协调、唯一约束或租约。
边界: 本地文件锁通常不能协调不同 CI 机器;“只创建一次”还涉及失败重试和进程崩溃。业务允许时,按 worker 创建独立资源往往比强制全局共享更稳健。参见xdist 会话级初始化说明。
51. 为什么 xdist 提示不同 worker 收集到的用例不一致?
热度:★★★★☆|难度:深入
参考答案: worker 需要收集到一致的测试项及顺序。参数来自无稳定顺序的集合、随机数、实时数据库、按当前时间变化的接口结果时,不同进程可能得到不同列表或 ID。
应将测试输入固定为本次运行的快照,使用稳定排序和稳定 ID;随机数据的生成需要明确种子和边界。资源的随机名称通常可以在 setup 时生成,而不是让它改变收集项。
排查方法: 对比各 worker 的 node ID 列表、数据版本和配置;先单独验证收集过程,再检查调度。不要通过无限重试 worker 来掩盖收集不确定性。
52. 如何测试 async def?pytest-asyncio 的 auto 与 strict 有什么区别?
热度:★★★★☆|难度:进阶
参考答案: Pytest 核心不能独自完成一般异步测试的事件循环执行,需要适配插件。使用 pytest-asyncio 时可以显式标记测试,并用专用装饰器声明异步 fixture。
|
|
strict 模式要求相应的异步测试标记及异步 fixture 声明,适合与其他异步插件共存;auto 模式会自动接管 asyncio 测试和相应 fixture。示例采用显式写法,减少对隐含配置的依赖。
易错点: 仅写 async def 并不保证测试被正确等待;异步测试也不意味着所有测试会自动并发执行。参见pytest-asyncio 概念说明。
53. 异步 fixture 的 scope 与 loop_scope 有什么区别?
热度:★★★☆☆|难度:深入
参考答案: fixture scope 管理对象缓存和清理边界,loop scope 管理异步代码使用哪个生命周期的事件循环。二者不是同一个参数。
例如 session 缓存的异步客户端可能绑定创建它的事件循环。若测试在其他循环中继续使用它,可能出现跨循环 Future 错误或 Event loop is closed。需要使客户端、fixture 和使用者的循环安排兼容,而不是只扩大 fixture scope。
pytest-asyncio 的循环配置和旧版本差异较大,当前文档要求 loop scope 不小于 fixture 的缓存 scope。新项目不要照搬旧教程里重写 event_loop fixture 的方案,应依据固定插件版本配置,并确保关闭客户端和取消后台任务。参见异步 fixture 装饰器。
54. 覆盖率、超时、重试和报告分别是谁提供的?
热度:★★★★☆|难度:进阶
| 能力 | 常见实现 | 注意事项 |
|---|---|---|
| JUnit XML | Pytest 内置 --junitxml |
适合 CI 结果接入 |
| 覆盖率 | pytest-cov 与 coverage.py |
要正确指定源码范围 |
| 并行 | pytest-xdist |
外部资源隔离 |
| 异步 asyncio | pytest-asyncio |
固定模式与循环配置 |
| Mock fixture | pytest-mock 的 mocker |
标准库 Mock 本身不依赖此插件 |
| 超时 | pytest-timeout 等 |
强制中断可能影响清理 |
| 失败重试 | pytest-rerunfailures 等 |
保留首次失败证据 |
| Allure 报告 | allure-pytest 与报告工具链 |
结果采集与报告生成是不同环节 |
参考答案: 要明确框架本体和插件边界。命令行参数不存在时,先检查插件是否安装在当前环境、版本是否支持及是否成功加载,不要直接归因于用例代码。
版本追问: pytest-cov 7.0 起移除了自身原有的子进程覆盖率支持,需按 coverage.py 的子进程方案配置;不能认为测了父进程就自动覆盖所有子进程。参见pytest-cov 子进程迁移说明。
六、实际项目:架构、数据、稳定性与 CI(55—72)
55. 从零搭建接口自动化项目,如何分层?
热度:★★★★★|难度:项目设计
场景: 有用户、订单、支付等多个模块,需要支持多个测试环境和日常回归。
参考答案: 先明确测试边界、运行环境和核心业务风险,再设计最小可用分层:
|
|
请求层负责协议细节,业务层表达创建订单等操作,测试层表达预期和边界,fixture 管理数据生命周期。公共断言可以封装重复规则,但不应把所有业务预期藏在工具内部。
加分点: 先做一条可独立运行、可清理、可报告的核心路径,再扩展;框架价值在于失败可解释、数据可隔离和维护成本可控,而不是目录或装饰器数量。
56. 登录只调用一次,token 过期和多角色怎么办?
热度:★★★★★|难度:项目设计
场景: 大多数用例需要登录,测试执行一小时,token 十分钟过期,还存在管理员与普通用户。
参考答案: 将认证放在专门提供者中,按环境、账号和角色隔离缓存,并记录有效期;客户端使用前检查是否接近过期,按策略刷新。session fixture 可以复用提供者,而不是永久复用一个不会变化的字符串。
刷新并发需要同步,避免多个请求同时刷新导致旧 token 失效。401 也可能代表权限、账号或服务问题,不能一律刷新并重发所有请求。
关键边界: 只有确认操作可安全重放时才自动重试;支付、创建资源等写请求需要幂等保障。注销、改密码等认证测试应使用独立账号,不能破坏共享登录态。xdist 下还需明确每个 worker 独立缓存或使用安全的集中式方案。
57. A 创建订单,B 使用订单,如何避免执行顺序依赖?
热度:★★★★★|难度:项目应用
参考答案: 把订单创建放到 fixture,使用者显式依赖它,保证 B 单独运行也能得到完整环境。如果要覆盖“下单 → 支付 → 退款”的完整状态迁移,可在一个场景测试里组织连续步骤。
独立接口行为测试和完整链路测试可以同时存在,但应明确各自目的。不要依赖文件名排序或排序插件来弥补缺失的前置条件。
追问: 共享一个订单会导致多个用例并发修改状态。默认按用例创建独立订单,只有只读资源才考虑高作用域共享;清理按业务允许的方式删除、取消或标记回收。
58. 如何隔离测试数据,避免并行冲突?
热度:★★★★★|难度:项目设计
参考答案: 为每次运行创建 run ID,再结合 worker ID、用例或随机标识构造唯一资源名;所有可变数据显式归属于本次运行,清理按归属处理,不能按宽泛前缀误删其他任务的数据。
隔离范围不仅是数据库主键,还包括手机号、邮箱、优惠券、库存、Redis key、消息消费者组、文件路径和端口。必要时按 worker 分配账号池或租户。
易错点: worker_id 只在一次运行内区分 worker,不足以区分两条同时执行的 CI 流水线。唯一名称解决冲突,权限、状态和容量配额仍需独立设计。
59. 数据库事务回滚能解决所有数据清理问题吗?
热度:★★★★★|难度:项目设计
参考答案: 同进程集成测试若被测代码和测试共用可控连接,可以通过事务和保存点回滚。但对远程 HTTP 服务发请求时,服务通常使用自己的连接并提交事务,测试侧的 rollback 无法撤销那些操作。
数据库回滚也不能直接撤销消息发布、缓存更新、对象存储写入和第三方请求。应根据测试边界选择事务隔离、独立数据库/租户、业务清理 API 或按运行标签回收。
项目追问: 被测代码自行提交、后台任务切换连接、DDL 或不同数据库的事务行为,都可能破坏预想的隔离。方案必须用真实写入与回滚验证,不能只因为用了 fixture 就认为数据不会泄漏。
60. 删除测试数据失败,怎样既保留原始失败又避免泄漏?
热度:★★★★☆|难度:项目设计
参考答案: 用例失败与清理失败都需要保留,不能用一个 except Exception: pass 把后者吞掉。清理应幂等,按依赖逆序执行,并尽量尝试释放剩余资源,而不是第一项失败后全部放弃。
可以维护清理清单,逐项收集异常,最后按项目支持的 Python 版本聚合报告;同时记录 run ID、资源 ID 和失败原因,交由受控回收机制补偿。
边界: 请求创建资源超时后,服务器可能已经创建成功,但客户端没拿到 ID。设计时要使用幂等键、业务关联号或可查询的测试标签,不能只清理“客户端确认成功”的资源。
61. 用例偶发失败,怎样判断是产品问题还是测试问题?
热度:★★★★★|难度:故障排查
参考答案: 首先保存首次失败的输入、时间线、请求关联号、服务版本和 worker 信息,再尝试最小化复现。按“单独运行是否失败、串行是否失败、特定顺序是否失败、是否集中在某环境”缩小范围。
常见测试原因包括共享状态、固定 sleep、未等待后台任务、时区与时间边界、随机输入不可复现、过严的时间断言;产品并发缺陷或一致性错误同样可能表现为偶发。
处理策略: 使用有限重试帮助收集证据;隔离不稳定用例时指定负责人、原因和期限,并报告隔离数量及首次失败率。不能直接把“重跑通过”解释为产品没有问题。参见官方不稳定测试讨论。
62. 订单异步处理,怎样避免 sleep(10)?
热度:★★★★★|难度:项目应用
参考答案: 使用有截止时间的条件轮询,查询目标资源达到明确终态后断言。固定 sleep 既可能等得太久,也可能在环境变慢时仍然不够。
|
|
关键边界: 这是同步轮询示例,单次 fetch 自身也必须有请求超时;否则外层截止时间不能中断一次永久阻塞的调用。生产项目要处理终态失败、退避和脱敏,异步测试应使用非阻塞等待。不要用宽泛异常捕获把真实错误无限忽略。
63. 请求超时后要不要重试?如何测试幂等性?
热度:★★★★★|难度:项目设计
参考答案: 超时只说明客户端没有按时获得完整响应,不证明服务器没有执行。查询类操作通常更容易安全重试,写操作应依据业务幂等协议处理,不能只按 GET/POST 名称机械判断。
针对创建订单或扣款,测试同一幂等键的重复请求、并发请求、首次响应丢失后的重放、不同参数复用同一键,以及键过期边界。断言不仅看响应,还要核验最终只产生一次预期副作用。
追问: 用 Mock 模拟超时可以覆盖客户端重试策略;要证明服务端幂等,需要真实集成测试和可观测的持久化/业务结果。请求层自动重试不能掩盖重复扣款风险。
64. 第三方支付、短信或不稳定外部服务怎样测试?
热度:★★★★★|难度:项目设计
参考答案: 分层处理:单元测试使用替身覆盖成功、拒绝、限流、超时和格式异常;契约测试验证客户端与协议一致;少量受控沙箱集成测试验证真实联通和关键流程。
设计适配层,让核心业务依赖明确接口,避免所有测试都依赖真实第三方可用性。测试不能调用真实付费或发信能力来验证基础逻辑,项目应有独立凭据和受限目标。
加分点: 替身响应要随协议变化维护,可从脱敏的固定样本构建;服务降级和失败恢复同样需要断言。只验证“返回了成功”不足以覆盖重复回调、签名校验和乱序事件。
65. 串行通过、并行失败,按什么顺序排查?
热度:★★★★★|难度:故障排查
参考答案: 先用 -n 0 确认串行基线,再用两个 worker 复现,记录失败测试、worker、资源名和时间线。按共享账号/数据、文件与端口、服务限流、初始化协调、收集一致性逐层检查。
若只在高并发失败,还要判断是否触发了真实容量或竞态问题,而不只是测试数据冲突。降低并发有助于定位,但不是自动证明测试设计正确。
临时措施与长期修复: 同文件调度或分组可缓解局部共享问题,但同 worker 不等于整个环境串行。长期应隔离资源、消除顺序依赖,并根据下游容量选择并发数。
66. 3000 条用例运行两小时,如何提速又不降低可信度?
热度:★★★★★|难度:项目优化
参考答案: 先测量耗时分布,区分收集、fixture 初始化、测试调用、清理和报告开销。可用 --durations=20 找热点,并记录请求次数和等待时间。
优先消除重复登录和重复准备,减少不必要的端到端重复覆盖,使用条件等待替代保守 sleep;只读昂贵资源可扩大作用域,可变数据保持隔离。之后再评估并行、分片和调度策略。
PR 流水线可执行快速高风险集合,夜间运行完整回归,但必须说明选择依据和漏检风险,不能把没执行的测试计为通过。指标同时看总耗时、首次失败率、资源成本和缺陷检出,避免“提速后报告更绿但覆盖更弱”。
67. 如何接入 CI?如何定位“本地通过、CI 失败”?
热度:★★★★★|难度:项目应用
|
|
参考答案: 固定解释器和依赖,准备必要服务与环境配置,执行后无论成功失败都上传报告和脱敏日志,并按退出码设置任务状态。产物上传步骤不能覆盖原始测试失败状态。
排查本地与 CI 的 Python/依赖版本、操作系统、工作目录、包安装、时区、locale、权限、环境变量、网络和并行配置。比较收集列表,使用同一个测试项和输入复现,而不是只比较启动命令。
追问: 干净 CI 环境通常没有本地缓存,且 Linux 路径大小写更严格;还要检查测试是否依赖未提交文件或执行顺序。定位时先追求环境可重建,再考虑增加重试。
68. 覆盖率 90% 为什么还会漏严重缺陷?如何制定门禁?
热度:★★★★☆|难度:项目设计
参考答案: 行覆盖率表示代码执行过,不表示断言有效,也不能保证边界、分支、并发和跨服务协议被验证。一个只调用函数而没有检查结果的测试也可能贡献覆盖率。
可结合分支覆盖、变更代码覆盖、关键规则用例和少量变异测试评估质量;设置分模块门槛,重点关注核心逻辑和新增代码。排除规则需要审查,避免通过排除难测代码美化数字。
|
|
关键边界: 远程接口自动化测的是另一个服务进程,本地客户端的覆盖率不能当成服务端覆盖率。服务端、子进程和分布式执行都需要独立正确的采集与合并方案。
69. 浏览器测试失败截图如何实现?常见坑有哪些?
热度:★★★★☆|难度:项目设计
参考答案: 浏览器由 fixture 创建并负责关闭,执行结果由报告 hook 获取。调用阶段失败时,在浏览器仍可使用的时点采集截图、当前页面、控制台或网络摘要,并将产物关联到测试项与阶段。
路径可由 run ID、worker ID 和安全化的测试项标识组成,避免多个 worker 覆盖同名文件。setup 失败时浏览器可能尚未创建,teardown 失败时也可能已关闭,必须检查可用性。
易错点: 截图失败覆盖了真正的断言异常;只记录 call 阶段而漏掉初始化失败;附件含密码、token 或个人信息。Allure 等报告插件负责附件展示,但不能替代资源生命周期设计。
70. 消息队列、缓存与最终一致性场景如何验证?
热度:★★★★☆|难度:项目设计
参考答案: 用唯一业务关联号跟踪一次操作,验证它最终产生预期业务结果。必要时使用独立 topic、队列、消费者组或命名空间,避免测试消费者抢走其他系统需要的消息。
等待应有截止时间,并区分处理中、成功终态和失败终态。重复投递场景要验证幂等,乱序场景要验证状态迁移约束;不能仅凭消费到一条消息就认定整个业务完成。
清理边界: 删除数据库记录并不代表已经清理在途消息或缓存。可以停止测试专属消费者、按 run ID 隔离数据并设置过期策略,但不能在共享环境里直接清空整个队列或 Redis。
71. 属性测试能补充参数化吗?适合哪些规则?
热度:★★★☆☆|难度:深入
参考答案: 参数化通常使用人工选择的有限示例;属性测试用工具生成输入,验证对一类输入成立的不变量。Hypothesis 等工具还能缩减失败输入,帮助定位最小反例,它不是 Pytest 核心自带的参数生成器。
适合编解码往返、排序结果有序且元素守恒、金额分配总和不变等性质。应先定义有效输入空间,避免测试充满无意义数据;失败时保留最小反例和相关版本。
易错点: 若业务预期本身不明确,随机生成更多输入也无济于事。对涉及远程副作用的测试,还需控制调用量和清理,不能直接把大量生成案例压到共享环境。人工边界例和属性测试应互相补充。
72. 面试官让你讲一个 Pytest 项目,怎样回答得具体可信?
热度:★★★★★|难度:综合表达
参考答案: 按“业务背景 → 原有问题 → 技术选择 → 实施细节 → 验证证据 → 后续限制”组织,用一个真实案例贯穿,而不是罗列插件名。
例如可以围绕“订单用例串行耗时高、并行又出现共享账号污染”展开:先通过耗时和失败日志定位重复认证与共享订单;将认证提供者与用例级订单 fixture 分开;为每次运行和 worker 隔离账号及业务数据;增加幂等清理;最后用独立重跑、并行回归和故障注入验证。
结果应引用真实记录中的耗时、首次失败率、清理失败数或维护成本,没有记录就坦诚说明,不能编造收益数字。最后讲清哪些测试仍需串行、哪些外部依赖还不稳定,以及为什么接受这些取舍。
高频追问准备: 你负责哪部分?为什么选这个 scope?worker 崩溃后谁清理?为什么不统一重试?怎么证明提速没有降低覆盖?这些问题比“用了哪些插件”更能体现实际经验。
七、易错说法纠正表
| 常见说法 | 更准确的表述 |
|---|---|
| session fixture 整个系统只执行一次 | 当前进程的会话生命周期;并行 worker 各有会话 |
| fixture 按测试参数左右顺序执行 | 按作用域、依赖和自动执行关系解析 |
| yield 后的清理永远执行 | 必须到达 yield;进程强杀等也可能绕过清理 |
| 重试通过就代表没有缺陷 | 只能说明再次执行通过,首次失败仍需分析 |
| xfail 就是不运行 | 通常仍执行,只是预期失败 |
| raises 是已知缺陷标记 | 它验证“抛出异常”这个正常预期 |
rootdir 会修复 Python 导入 |
它不自动修改导入搜索路径 |
| 用事务就能撤销远程接口写入 | 测试事务不能覆盖服务独立连接中的提交 |
-n auto 是 Pytest 内置参数 |
来自 pytest-xdist 插件 |
mocker 是内置 fixture |
来自 pytest-mock;monkeypatch 才是内置 |
| 异步测试天然同时执行 | 事件循环执行能力与测试并发调度是两件事 |
| 覆盖率高就代表断言质量高 | 覆盖率只反映执行范围,还需检查预期和风险 |
| Mock 原始定义位置总能生效 | 必须替换被测代码实际查找名称的位置 |
| 所有用例都可以直接重试 | 写入副作用、幂等性和故障证据必须先考虑 |
八、复习与现场练习清单
| 阶段 | 推荐题目 | 应达到的程度 |
|---|---|---|
| 入门过关 | 1—5、7—13、17—18、25、30—31 | 能写测试函数、fixture、参数化和基本断言 |
| 中级扎实 | 14—16、19—24、26—29、35—44 | 能解释生命周期、数据隔离和 Mock 边界 |
| 进阶区分 | 33—34、45—54 | 能读懂 hook,解释并行与异步问题 |
| 项目表达 | 55—72 | 能说清真实约束、方案取舍、验证证据 |
建议亲手完成以下练习,而不只背答案:
- 写一个工厂 fixture,创建多个资源,模拟中途失败并验证已创建资源仍被清理。
- 写三种参数化形式:普通、fixture 参数化、间接参数化;检查生成的 node ID。
- 用两个模块演示“patch 定义位置失败、patch 查找位置成功”。
- 编写一个 hook,同时记录 setup、call、teardown 的结果,分别制造三类失败。
- 对同一组测试分别串行、双 worker 执行,观察 session 初始化次数和资源命名。
- 写一个有期限的条件等待,并测试成功、失败终态、超时和查询异常。
- 用一个自己的实际项目回答第 72 题,准备日志或真实指标支撑取舍。
九、官方资料索引
以下链接用于核对 API 和版本差异;星级与项目设计建议属于本文的经验整理,不是官方的面试排名。