这三个库是Python测试生态的核心组合 ,分工明确且配合无缝:pytest==9.0.2是基础测试框架(负责用例编写、执行),pytest-asyncio==1.3.0是异步测试插件(让pytest支持async def用例),pytest-cov==7.0.0是覆盖率统计插件(执行测试时统计代码被覆盖的比例),且这三个版本完全兼容,能直接搭配使用。
下面从安装→基础用例编写(同步+异步)→命令行执行(含覆盖率统计)→核心进阶用法一步步讲,所有示例开箱即用,适配你的指定版本。
一、先统一安装指定版本
打开终端,执行以下命令(国内源加速,避免下载失败),一次性安装三个库:
bash
pip install pytest==9.0.2 pytest-asyncio==1.3.0 pytest-cov==7.0.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完成后,终端输入pytest --version显示9.0.2,说明环境配置成功。
二、核心规则:pytest的用例编写基础
不管是同步还是异步用例,pytest都有极简的命名规则,无需继承类、无需写繁琐的测试框架代码,符合规则的代码会被自动识别为测试用例:
- 测试文件命名:以
test_*.py或*_test.py结尾(如test_demo.py); - 测试函数命名:以
test_开头(如test_add()); - 测试类命名:以
Test开头(且类名无下划线,如TestMath),类内测试方法同样以test_开头; - 断言:直接用Python原生
assert语句(比unittest的self.assertEqual更简洁)。
三、同步用例:纯pytest==9.0.2的基础使用
先从最简单的同步代码测试入手,理解pytest的核心用法,适合测试普通的同步函数/类(如工具函数、同步接口等)。
步骤1:编写业务代码和测试用例
新建项目目录,结构如下(简单的数学工具函数为例):
your_project/
├── math_utils.py # 业务代码:待测试的同步函数
└── test_math.py # 测试文件:pytest测试用例
-
业务代码
math_utils.py:python# 待测试的同步函数 def add(a, b): return a + b def sub(a, b): return a - b -
测试用例
test_math.py:按pytest规则编写,直接用assert断言结果:pythonfrom math_utils import add, sub # 测试函数:以test_开头 def test_add(): # 断言:预期结果=实际结果 assert add(1, 2) == 3 assert add(0, 0) == 0 assert add(-1, 1) == 0 def test_sub(): assert sub(5, 3) == 2 assert sub(2, 5) == -3
步骤2:命令行执行同步测试
打开终端,cd到项目根目录 (your_project/),执行核心命令:
bash
# 最简执行:自动查找所有测试用例并执行
pytest
执行结果说明(关键信息):
- 会显示收集到的用例数 (如
collected 2 items); - 用
./F/E标识结果:.表示用例通过,F表示断言失败,E表示代码报错; - 最后显示总结果 (如
2 passed in 0.01s),无报错即测试通过。
四、异步用例:结合pytest-asyncio==1.3.0使用
原生pytest无法直接运行async def定义的异步函数,pytest-asyncio的核心作用是让pytest支持异步用例/异步夹具(fixture) ,核心仅需一个装饰器@pytest.mark.asyncio(pytest-asyncio 1.3.0+无需额外配置,直接用)。
适合测试异步代码:如asyncio协程、异步IO(文件/网络)、FastAPI/Starlette异步接口、异步数据库操作等。
步骤1:编写异步业务代码和异步测试用例
在原有项目中新增异步业务文件和测试文件,结构如下:
your_project/
├── math_utils.py # 同步业务代码
├── async_math_utils.py # 异步业务代码:待测试的async函数
├── test_math.py # 同步测试用例
└── test_async_math.py # 异步测试用例:结合pytest-asyncio
-
异步业务代码
async_math_utils.py:pythonimport asyncio # 待测试的异步函数(模拟异步操作:如异步数据库/接口调用) async def async_add(a, b): # 模拟异步延迟 await asyncio.sleep(0.1) return a + b async def async_sub(a, b): await asyncio.sleep(0.1) return a - b -
异步测试用例
test_async_math.py:核心加@ pytest.mark.asyncio装饰器 ,用例直接定义为async def,断言和同步一致:pythonimport pytest from async_math_utils import async_add, async_sub # 异步测试用例:必须加@pytest.mark.asyncio装饰器 @pytest.mark.asyncio async def test_async_add(): # 异步函数需用await调用 assert await async_add(1, 2) == 3 assert await async_add(-2, 3) == 1 @pytest.mark.asyncio async def test_async_sub(): assert await async_sub(10, 4) == 6 assert await async_sub(3, 7) == -4
步骤2:命令行执行异步测试
和同步测试命令完全一致,pytest会自动识别@pytest.mark.asyncio装饰的用例并执行异步调度:
bash
# 执行所有测试(同步+异步)
pytest
# 仅执行异步测试文件(精准执行)
pytest test_async_math.py -v
- 加
-v是详细模式,会显示每个用例的执行结果(推荐日常使用,更清晰); - 异步用例的执行速度和同步几乎一致,pytest-asyncio会自动管理事件循环,无需手动创建
asyncio.run()。
五、覆盖率统计:结合pytest-cov==7.0.0使用
pytest-cov是基于coverage.py封装的pytest插件,执行测试的同时自动统计代码覆盖率 ,核心统计「行覆盖率」(业务代码中多少行被测试用例执行过),能生成终端报告、HTML可视化报告(最常用),方便排查「代码死角」(未被测试覆盖的代码)。
核心命令:执行测试+统计覆盖率
所有命令均在项目根目录执行 ,核心参数是--cov=包/模块名,指定要统计的业务代码(而非测试代码),避免覆盖率被测试代码稀释。
1. 基础用法:终端显示覆盖率报告
统计math_utils.py和async_math_utils.py的覆盖率,执行所有测试并在终端输出结果:
bash
# --cov=./ 表示统计当前目录下所有业务代码的覆盖率(推荐简单项目)
pytest --cov=./ -v
终端报告关键信息解读:
---------- coverage: platform win32, python 3.10.11 ----------
Name Stmts Miss Cover
-----------------------------------------
async_math_utils.py 8 0 100%
math_utils.py 4 0 100%
test_async_math.py 10 0 100%
test_math.py 8 0 100%
-----------------------------------------
TOTAL 30 0 100%
Stmts:代码总行数;Miss:未被测试覆盖的行数;Cover:覆盖率;- 上面示例中所有代码100%覆盖,说明测试用例覆盖了所有业务逻辑。
2. 进阶用法1:排除测试文件,只统计业务代码
实际开发中,不需要统计测试文件(test_*.py)的覆盖率 ,用--cov-exclude=匹配规则排除,或直接指定业务代码目录:
bash
# 方式1:排除所有test_*.py文件
pytest --cov=./ --cov-exclude="test_*.py" -v
# 方式2:只统计指定的业务文件(更精准)
pytest --cov=math_utils --cov=async_math_utils -v
3. 进阶用法2:生成HTML可视化报告(最推荐)
终端报告不够直观,--cov-report=html会在项目根目录生成htmlcov/文件夹,打开其中的index.html即可在浏览器中可视化查看覆盖率(红色行=未覆盖,绿色行=已覆盖),适合团队协作和项目排查:
bash
# 核心参数:--cov-report=html 生成HTML报告
pytest --cov=./ --cov-exclude="test_*.py" --cov-report=html -v
使用HTML报告:
- 执行命令后,项目根目录会出现
htmlcov/文件夹; - 打开该文件夹中的
index.html(用任意浏览器); - 点击对应的业务文件(如
async_math_utils.py),即可看到每行代码的覆盖情况,红色行就是需要补充测试用例的地方。
4. 精准执行:指定文件+统计覆盖率
仅测试某个文件,同时统计该文件对应的业务代码覆盖率:
bash
# 仅执行test_async_math.py,同时统计async_math_utils的覆盖率
pytest test_async_math.py --cov=async_math_utils --cov-report=html -v
六、pytest核心进阶用法(日常开发高频使用)
结合你的指定版本,补充3个最实用的pytest功能,适配同步/异步用例,大幅提升测试效率:
1. 只执行失败的用例:--lf
当项目有大量测试用例,部分用例失败时,无需重新执行所有用例,用--lf(last failed)仅执行上一次失败的用例:
bash
pytest --lf -v
# 结合覆盖率:只执行失败用例,同时统计覆盖率
pytest --lf --cov=./ -v
2. 标记用例并分组执行:@pytest.mark
给测试用例打标签(如@pytest.mark.unit(单元测试)、@pytest.mark.async(异步测试)),实现分组执行(如只执行单元测试,不执行异步测试),适合大型项目。
步骤1:给用例打标记
修改test_math.py和test_async_math.py,添加标记:
python
# test_math.py 同步用例:打unit标记
import pytest
from math_utils import add, sub
@pytest.mark.unit
def test_add():
assert add(1, 2) == 3
@pytest.mark.unit
def test_sub():
assert sub(5, 3) == 2
# test_async_math.py 异步用例:打async标记
import pytest
from async_math_utils import async_add, async_sub
@pytest.mark.async
@pytest.mark.asyncio # 异步用例需同时保留asyncio标记
async def test_async_add():
assert await async_add(1, 2) == 3
@pytest.mark.async
@pytest.mark.asyncio
async def test_async_sub():
assert await async_sub(10, 4) == 6
步骤2:分组执行用例
用-m 标记名执行指定分组的用例,-m "not 标记名"排除指定分组:
bash
# 只执行标记为unit的同步用例
pytest -m unit -v
# 只执行标记为async的异步用例
pytest -m async -v
# 执行所有用例,排除async标记的用例
pytest -m "not async" -v
3. 夹具Fixture:复用测试前置/后置逻辑
Fixture是pytest的核心特性,用于复用测试的前置操作(如初始化数据库、创建连接)和后置操作(如关闭连接、清理数据),支持同步/异步Fixture,适配pytest-asyncio。
示例:同步Fixture(复用初始化逻辑)
修改test_math.py,添加一个返回测试数据的Fixture,多个用例可复用:
python
import pytest
from math_utils import add, sub
# 定义Fixture:以fixture装饰,scope指定作用域(function=每个用例执行一次,class=每个类,module=每个文件)
@pytest.fixture(scope="function")
def test_data():
# 前置操作:准备测试数据
data = {"a": 10, "b": 5}
print("\n测试数据初始化完成")
yield data # yield返回数据,后续是后置操作
# 后置操作:清理数据(如关闭连接、删除文件)
print("\n测试数据清理完成")
# 测试用例传入Fixture名,即可使用Fixture的返回值
@pytest.mark.unit
def test_add_with_fixture(test_data):
assert add(test_data["a"], test_data["b"]) == 15
@pytest.mark.unit
def test_sub_with_fixture(test_data):
assert sub(test_data["a"], test_data["b"]) == 5
执行命令pytest test_math.py::test_add_with_fixture -v,会看到Fixture的前置→用例执行→后置流程自动执行。
示例:异步Fixture(结合pytest-asyncio)
修改test_async_math.py,定义异步Fixture(适配异步用例,需加@pytest.mark.asyncio):
python
import pytest
from async_math_utils import async_add, async_sub
# 异步Fixture:async def定义,加pytest.fixture装饰
@pytest.fixture(scope="function")
async def async_test_data():
# 异步前置操作:如异步连接数据库
data = {"a": 20, "b": 8}
print("\n异步测试数据初始化完成")
yield data
# 异步后置操作:如异步关闭数据库连接
print("\n异步测试数据清理完成")
# 异步用例传入异步Fixture,直接await使用
@pytest.mark.async
@pytest.mark.asyncio
async def test_async_add_with_fixture(async_test_data):
assert await async_add(async_test_data["a"], async_test_data["b"]) == 28
执行后,异步Fixture会和异步用例一起被pytest-asyncio调度,自动执行异步前置/后置逻辑。
七、三个库的组合命令汇总(日常开发直接复制)
整理最常用的同步+异步测试+覆盖率统计组合命令,覆盖80%的使用场景:
| 需求场景 | 核心命令 |
|---|---|
| 执行所有测试(详细模式) | pytest -v |
| 仅执行异步测试文件 | pytest test_async_math.py -v |
| 执行所有测试+终端覆盖率报告 | pytest --cov=./ --cov-exclude="test_*.py" -v |
| 执行所有测试+HTML可视化覆盖率报告 | pytest --cov=./ --cov-exclude="test_*.py" --cov-report=html -v |
| 只执行unit标记的用例+统计覆盖率 | pytest -m unit --cov=math_utils --cov-report=html -v |
| 只执行上一次失败的用例+统计覆盖率 | pytest --lf --cov=./ -v |
八、关键注意点(避坑,适配你的指定版本)
- pytest-asyncio 1.3.0 :异步用例必须加
@pytest.mark.asyncio装饰器 ,无需额外配置事件循环(插件自动管理),异步Fixture直接用async def定义即可; - pytest-cov 7.0.0 :统计覆盖率时一定要排除测试文件(test_*.py),否则覆盖率结果会偏高,误导排查;
- pytest 9.0.2 :完全兼容Python3.8+,舍弃了Python2的支持,Fixture的
scope参数新增了package作用域(包级别),可按需使用; - 断言失败 :pytest会自动显示预期值和实际值的差异,无需手动打印,直接根据报错信息修改用例即可。
总结
这三个库的使用核心围绕**「pytest为基础,插件做扩展」**,整体流程极简:
- 按规则编写同步用例 (
test_开头)、异步用例 (加@pytest.mark.asyncio的async def函数); - 用
pytest 文件名/标记执行指定用例,加-v看详细结果; - 加
--cov系列参数结合pytest-cov统计覆盖率,--cov-report=html生成可视化报告; - 用Fixture 复用测试前置/后置逻辑,用标记实现用例分组执行。
这套组合是Python单元测试、异步代码测试的工业级标准方案,小到工具函数测试,大到FastAPI/异步项目测试都能覆盖。