[Python]pytest是什么?执行逻辑是什么?为什么要用它测试?
在vscode中创建项目、虚拟环境,安装项目并添加到工作空间完整步骤来了-CSDN博客这篇文章里面通过cookiecutter创建项目后除了源代码src目录外,还有tests目录,而且在手动创建包后也创建了这个目录,在项目目录下运行了pytest命令,那么pytest是什么呢?起到什么作用呢?
1、pytest是什么?
pytest 是一个强大的 Python 测试框架。它的核心目的非常简单:让编写和运行测试变得简单、高效、可读性强。
具体来说,它提供了以下功能:
-
自动发现测试:你不需要手动注册或导入所有测试用例。pytest 会自动从当前目录及其子目录中查找以
test_
开头的文件,并运行这些文件中以test_
开头的函数或方法。 -
丰富的断言:你只需要使用标准的
assert
语句即可,pytest 会提供非常详细的错误信息,帮助你快速定位问题。你不再需要记忆像assertEqual
,assertTrue
这样的各种断言方法(这是 unittest 框架的方式)。 -
夹具系统 (Fixtures):这是 pytest 的王牌功能。它提供了一种优雅的方式来设置测试前的准备工作和测试后的清理工作(比如创建数据库连接、初始化对象、清理临时文件等),使得测试代码更模块化、可复用。
-
参数化测试:可以用一个函数来测试多组不同的输入和输出,避免写大量重复的测试代码。
-
丰富的插件生态系统:有大量插件可以扩展 pytest 的功能,如测试覆盖率(pytest-cov)、并行测试(pytest-xdist)、 mock(pytest-mock)等。
简单总结:pytest 的目的是提供一个“一站式”的解决方案,让你能专注于写测试逻辑本身,而不是繁琐的测试框架配置。
2、pytest执行逻辑是什么?
pytest 的默认行为是递归搜索当前工作目录及其所有子目录,寻找:
-
文件名以
test_
开头 或者 以_test.py
结尾 的.py
文件。 -
在上述文件中,找到以
test_
开头的函数。 -
找到以
Test
开头的类(并且不能有__init__
方法)中的以test_
开头的方法。
所以,只要你把测试文件放在 tests
目录下,并且遵循上述命名规则,直接在项目根目录下运行 pytest
命令,它就能自动发现并运行 tests
目录下的所有测试。
最佳实践:通常项目的目录结构如下:
my_project/
├── src/ # 你的项目源代码
│ └── my_package/
│ ├── __init__.py
│ └── module.py
├── tests/ # 你的测试代码
│ ├── __init__.py # (可选,取决于你的导入策略)
│ ├── test_module.py
│ └── conftest.py # (存放fixture的专用文件)
├── pyproject.toml # 或 setup.py
└── README.md
在项目根目录 (my_project/
) 下运行 pytest
即可。
3、如何测试我src的代码呢?
一个非常重要的概念:测试代码和源代码是分离的。
tests
目录下的文件是纯粹的测试代码,它们的目的是导入并测试 src
目录下的源代码。
做法是:
在你的测试文件(如 tests/test_module.py
)中,通过 import 语句来导入你想要测试的源代码模块。
例如,你的源代码在 src/my_package/module.py
中有一个函数:
# src/my_package/module.py
def add(a, b):return a + b
你的测试文件应该这样写:
# tests/test_module.py
from my_package.module import add # 导入你要测试的函数def test_add():result = add(2, 3)assert result == 5
要直接运行 pytest
如果找不到 my_package
这个模块,记得在项目根目录下进行包的可编辑安装,同样在前面提到的文章中有描述,可编辑安装命令如下:
pip install -e .
4、pytest能测试哪些内容?
pytest 既可以用来测试单个函数(单元测试),也可以用来测试多个模块的组合(集成测试),甚至整个应用(端到端测试)。
-
测试单个函数(单元测试):这是 pytest 最常用的场景,就像上面的
test_add()
例子。你隔离一个最小的代码单元(通常是一个函数或方法)进行测试。 -
测试多个组件(集成测试):你可以写一个测试函数,这个函数会调用多个你写的函数或类,测试它们在一起协作时是否能正常工作。
-
测试整个功能(端到端测试):例如,对于一个 Web 应用,你可以用 pytest 配合
selenium
或requests
库来模拟用户操作,测试从前端到后端的整个流程。
pytest 的夹具(Fixture)系统尤其适合构建复杂测试场景所需的“脚手架”,比如为一个集成测试初始化数据库、启动一个测试服务器等。
5、“为什么要写测试”的根本
如果就写一个简单的py文件,直接在py文件里面写了再测试调用一下就行了,估摸着也不用pytest这个框架,但是如果复杂一些的项目还是需要的,直接运行代码(我们称之为“手动测试”)和编写自动化测试(如使用pytest)有本质的区别。
1. 规模与效率 (Scale & Efficiency)
-
直接运行:你的项目有100个函数。你修改了其中1个。为了确保没破坏其他99个,你需要手动一个一个去运行、检查它们的输出。这个过程极其枯燥、耗时,且容易出错和遗漏。
-
pytest自动化测试:你只需要在终端输入
pytest
。一秒钟之内,计算机会自动运行所有100个测试,并给你一个清晰的报告:“通过了98个,失败了2个”。效率是天壤之别。
2. 回归测试 (Regression Testing)
这是自动化测试最核心的价值。你担心修改代码后引入新的Bug(这被称为“回归”)。
-
直接运行:每次修改代码后,你都需要依靠记忆和毅力去重新测试所有可能受影响的功能。项目稍大一点,这根本不可能完成。
-
pytest自动化测试:
pytest
命令就是你的“回归安全网”。每次修改代码后(无论是修复Bug还是添加新功能),你运行一下pytest,它能立即告诉你,你的修改是否意外地搞坏了之前本来正常的功能。这给了你重构和改进代码的巨大信心。
3. 可靠性与准确性 (Reliability & Accuracy)
-
直接运行:人会疲劳、会分心、会犯错。你可能今天测试了边界条件,明天就忘了。你可能看输出结果时眼花看错了。
-
pytest自动化测试:计算机永远不会累,永远不会分心。测试用例一旦写好,就会以完全相同的方式、检查完全相同的条件每一次都执行。它用
assert
语句做精确的判断,不存在“好像差不多”这种模糊概念。
4. 充当活文档 (Living Documentation)
-
直接运行:一个新同事接手你的代码,他怎么知道每个函数应该怎么用?输入什么?预期输出什么?他只能看代码逻辑或者问你(如果你还在这家公司的话)。
-
pytest自动化测试:测试用例本身就是最好的用法示例。
test_add()
函数清楚地展示了add()
函数应该如何被调用,以及给定输入(2, 3)
时期望得到输出5
。测试集构成了一个可执行的、永远不会过时的文档。
5. 设计促进 (Design Improvement)
编写可测试的代码会迫使你写出更好的代码。为了便于测试,你会自然而然地:
-
将函数设计成功能单一(只做一件事)。
-
减少函数之间的紧耦合(依赖过多)。
-
明确函数的输入和输出,而不是依赖外部隐藏状态。
这些特性都是良好软件设计的关键原则。
6.具体的场景对比
如果上面的表述比较晦涩的话,我们看一个具体场景,假设你有一个函数,用于计算列表中大于某值的元素个数:
# src/my_package/module.py
def count_above(numbers, threshold):count = 0for num in numbers:if num > threshold:count += 1return count
方法一:直接运行测试(手工)
你在Python解释器里或者写个临时脚本:
from src.my_package.module import count_aboveresult = count_above([1, 5, 8, 2, 10], 5)
print(result) # 输出 3。嗯,对了。
然后...你就把这个脚本丢了。明天你修改了函数逻辑,除非你记得重新测试这个案例,否则你无法知道修改是否正确。
方法二:pytest自动化测试
你编写一个测试文件 tests/test_module.py,注意下面的测试都是在测试src中的count_above函数。
from my_package.module import count_abovedef test_count_above_basic():result = count_above([1, 5, 8, 2, 10], 5)assert result == 3def test_count_above_with_negative():result = count_above([-5, -1, 0, 7], 0)assert result == 1def test_count_above_empty_list():result = count_above([], 10)assert result == 0def test_count_above_threshold_in_list():result = count_above([1, 2, 3, 4, 5], 3)assert result == 2
现在,两者的区别显而易见:
-
覆盖度:自动化测试一次性考虑了多种情况(基础情况、负数、空列表、边界值),而手动测试通常只验一两种 happy path。
-
持久性:测试用例被永久保存了下来。
-
回归能力:未来任何修改,运行
pytest
都能确保这四种情况依然正确。 -
文档性:任何人看
test_count_above_
开头的函数,都能立刻明白count_above
函数的设计意图和用法。
结论
直接运行代码是一种“试探”,而编写自动化测试是一种“保障”。
前者适用于写代码时快速的、一次性的逻辑验证(“这样写通不通?”)。而后者是软件工程中保证长期质量、维护性和可靠性的基石,是专业开发者必备的实践。它节省的是未来无数小时的Debug时间,避免的是项目在无人敢动的“屎山”深渊滑落。