Python测试实战:pytest项目结构、Mock与自动化测试指南
1. 项目概述:为什么你需要一个专业的pytest测试项目指南
如果你是一名Python开发者,无论你是刚入门的新手,还是已经写过一些 unittest 的老手,当你开始接触一个稍具规模的项目时,很快就会发现:写测试本身不难,难的是把测试写得高效、可维护,并且能融入整个开发流程。这就是为什么一个结构清晰、约定俗成的“pytest测试项目指南”如此重要。它不仅仅教你 pytest 的语法,更是教你如何用测试驱动设计、如何组织测试代码、如何利用各种插件提升效率,最终构建一个坚如磐石的软件质量保障体系。
我见过太多项目,测试文件散落各处, conftest.py 里塞满了不知所谓的 fixture ,运行一次测试要等好几分钟。这通常是因为从一开始就缺乏一个顶层的设计。一个好的测试项目,应该像你的生产代码一样,有清晰的结构、明确的职责和高效的执行路径。 pytest 框架本身非常灵活,但“能力越大,责任越大”,如果没有良好的实践指南,很容易陷入混乱。
本文将从一个真实的、可复现的Python项目(比如一个简单的ETL数据管道)出发,带你一步步搭建一个专业的pytest测试项目。我们会涵盖从项目结构、测试编写、依赖模拟(Mock)、参数化测试、覆盖率检查,到代码风格检测和自动化测试流水线(tox)的完整闭环。我的目标是,你读完这篇文章后,不仅能复制出一个标准的测试项目骨架,更能理解每一个决策背后的“为什么”,从而有能力为你自己的项目定制最合适的测试策略。
2. 测试项目的顶层设计与核心思路
在动手写第一行测试代码之前,我们必须想清楚这个测试项目要达成什么目标。对于大多数Python应用,测试的核心目标无外乎这几点:验证功能正确性(这是根本)、防止回归错误(改了这里,那里别坏)、作为活文档(测试即用例)、以及为重构提供信心。基于这些目标,我们的测试项目设计需要遵循几个核心原则。
2.1 测试隔离与可重复性
这是测试的黄金法则。每一个测试用例都应该是独立的,不依赖于外部环境(如数据库、网络服务)的特定状态,也不受其他测试用例执行结果的影响。 pytest 默认的运行机制保证了测试函数的独立性,但我们需要在代码层面主动维护这一点。这意味着要大量使用 fixture 来准备和清理测试数据,使用 pytest-mock 来隔离对外部服务的调用。例如,测试一个从API获取数据的函数,你应该 mock 掉 requests.get ,返回预设的模拟数据,而不是真的去调用一个可能不稳定、有速率限制的外部API。
2.2 测试结构反映代码结构
一个好的实践是让测试代码的结构镜像生产代码的结构。如果你的源代码放在 src/myapp/ 下,里面有 core/ , utils/ , api/ 等模块,那么你的 tests/ 目录下最好也有对应的 test_core/ , test_utils/ , test_api/ 。这样做的好处非常直观:找测试容易,维护关系清晰。对于 src/myapp/core/calculator.py ,其对应的测试文件就是 tests/test_core/test_calculator.py 。这种一一对应的关系,让新加入团队的开发者也能快速定位。
2.3 利用配置与共享设施(conftest.py)
conftest.py 是 pytest 的精髓之一,它是一个用于存放 fixture 和插件配置的本地文件。 pytest 会自动发现项目根目录和各级子目录下的 conftest.py 。我们可以利用它来实现不同层次的 fixture 共享。比如,在项目根目录的 conftest.py 中定义整个项目都可能用到的 fixture ,如数据库连接、HTTP客户端模拟;在 tests/api/ 子目录下的 conftest.py 中,定义专门用于API测试的 fixture ,如认证token。这避免了 fixture 的重复定义,也使得测试代码更加模块化。
2.4 追求高覆盖率,但更关注有效覆盖
测试覆盖率(Coverage)是一个重要的量化指标,它能告诉你有多少代码被测试执行过。 pytest-cov 插件可以很方便地生成覆盖率报告。我们应该追求高覆盖率(比如90%以上),但必须清醒地认识到,覆盖率只是一个必要条件,而非充分条件。覆盖了100%的代码行,不代表没有bug。我们的关注点应该放在“有效覆盖”上,即测试是否覆盖了核心的业务逻辑、边界条件和异常路径。一个只测试了 if 语句 True 分支而没测 False 分支的测试,即使覆盖率显示行已覆盖,其价值也是打折扣的。
3. 搭建测试项目骨架与核心工具链
现在,让我们从一个干净的Python项目开始,一步步搭建起测试环境。假设我们的项目名为 example_etl ,是一个简单的数据提取、转换、加载管道。
3.1 初始化项目与依赖管理
首先,使用 poetry (或 pipenv , 这里以 poetry 为例)初始化项目并管理依赖。 poetry 能很好地隔离开发依赖和生产依赖。
# 初始化项目
poetry new example_etl --src
cd example_etl
# 添加核心开发依赖:pytest及常用插件
poetry add --group dev pytest pytest-mock pytest-cov
这里我们一次性添加了三个核心插件:
pytest: 测试框架本体。pytest-mock: 提供了mockerfixture,用于模拟对象,比标准库的unittest.mock更贴合pytest风格。pytest-cov: 用于生成测试覆盖率报告。
3.2 创建标准的项目目录结构
一个清晰的结构是成功的一半。我推荐如下结构:
example_etl/
├── pyproject.toml # 项目配置和依赖声明
├── README.md
├── src/ # 生产代码
│ └── example_etl/
│ ├── __init__.py
│ ├── core/ # 核心业务模块
│ └── utils/ # 工具函数
└── tests/ # 测试代码
├── __init__.py
├── conftest.py # 项目级共享fixture
├── test_core/ # 对应src/example_etl/core/
└── test_utils/ # 对应src/example_etl/utils/
注意 tests/ 目录下也有 __init__.py ,这将其变为一个Python包,在某些工具(如某些覆盖率工具)下工作得更好。 src 布局(将包放在 src 下)能避免在开发时无意中从当前目录导入模块而非安装的包,这是一个最佳实践。
3.3 编写第一个conftest.py
在 tests/conftest.py 中,我们可以定义一些最基础的、全局可用的 fixture 。例如,一个用于模拟 click.CliRunner 的 fixture ,因为很多命令行工具测试会用到它。
# tests/conftest.py
import tempfile
import pytest
from click.testing import CliRunner
@pytest.fixture
def clicker():
"""提供一个CliRunner实例,用于测试click命令行应用。"""
return CliRunner()
@pytest.fixture
def temp_json_file():
"""创建一个临时的JSON文件,并在测试后自动清理。"""
import json
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump({"test": "data"}, f)
temp_path = f.name
yield temp_path
# 测试结束后,清理临时文件
import os
os.unlink(temp_path)
fixture 使用 yield 语句, yield 之前是设置代码,之后是清理代码。 pytest 会确保清理代码无论测试成功与否都会执行。
3.4 配置pytest运行选项
在 pyproject.toml (或单独的 pytest.ini )中配置 pytest ,可以让团队所有成员使用统一的测试行为。
# pyproject.toml 的 [tool.pytest.ini_options] 部分
[tool.pytest.ini_options]
testpaths = ["tests"] # 告诉pytest在哪里找测试
addopts = [
"-v", # 详细输出
"--strict-markers", # 严格检查marker,避免拼写错误
"--tb=short", # 错误回溯信息简短模式,更清晰
]
python_files = "test_*.py" # 测试文件命名模式
python_classes = "Test*" # 测试类命名模式
python_functions = "test_*" # 测试函数命名模式
[tool.pytest.ini_options.markers]
slow: "标记运行缓慢的测试(可通过 `pytest -m 'not slow'` 跳过)"
integration: "标记集成测试"
4. 深入pytest测试编写:从单元测试到集成测试
有了项目骨架,我们开始编写真正的测试。我们将遵循“测试金字塔”理念,从底层的单元测试开始。
4.1 编写坚实的单元测试
单元测试的目标是隔离地测试一个函数、一个方法或一个类的行为。我们以 src/example_etl/transformer/strip.py 中的一个字符串处理类为例。
# src/example_etl/transformer/strip.py
class StripTransformer:
def __init__(self, config):
self.config = config
def transform(self, data: str) -> str:
"""去除字符串两端的空白字符。"""
if not isinstance(data, str):
raise ValueError("Input data must be a string")
return data.strip()
对应的单元测试文件 tests/test_transformer/test_strip.py :
# tests/test_transformer/test_strip.py
import pytest
from example_etl.transformer.strip import StripTransformer
# 使用fixture来创建被测试对象,避免重复代码
@pytest.fixture
def strip_transformer(mocker):
# mocker fixture 由 pytest-mock 提供
mock_config = mocker.MagicMock()
return StripTransformer(mock_config)
# 测试正常功能
def test_strip_transformer_removes_spaces(strip_transformer):
result = strip_transformer.transform(" hello ")
assert result == "hello"
# 参数化测试:用一组输入输出高效测试多种情况
@pytest.mark.parametrize(
"input_data, expected_output",
[
(" hello ", "hello"),
("hello", "hello"),
("\t\nhello\r\n", "hello"),
("", ""),
]
)
def test_strip_transformer_various_inputs(strip_transformer, input_data, expected_output):
result = strip_transformer.transform(input_data)
assert result == expected_output
# 测试异常路径:输入非字符串应抛出异常
def test_strip_transformer_invalid_input(strip_transformer):
with pytest.raises(ValueError, match="Input data must be a string"):
strip_transformer.transform(123)
关键点解析:
- 使用
fixture创建测试对象 :strip_transformerfixture封装了对象的创建逻辑。如果未来StripTransformer的构造函数发生变化,只需修改这一处fixture。 - 参数化测试
@pytest.mark.parametrize:这是pytest的杀手锏之一。它将多组测试数据注入到一个测试函数中,极大地减少了重复代码,让测试用例更清晰。 -
pytest.raises上下文管理器 :用于断言代码块会抛出特定的异常。match参数可以进一步断言异常信息中包含特定文本。 - 断言使用Python原生
assert:pytest会重写assert语句,在断言失败时提供极其丰富的上下文信息,这是它比unittest的self.assertEqual()更友好的地方。
4.2 使用Mock隔离依赖
单元测试要求“隔离”。当你的函数调用了数据库、网络请求、文件系统或其他模块时,你需要用 Mock 对象来替换这些依赖。 pytest-mock 提供的 mocker fixture让这一切变得简单。
假设我们有一个从外部服务获取数据的函数:
# src/example_etl/extractor/api.py
import requests
class ApiExtractor:
def __init__(self, config):
self.config = config
self.api_url = config.API_URL
def extract(self):
response = requests.get(self.api_url)
response.raise_for_status()
return response.json()
测试这个类时,我们绝不能真的发起网络请求。测试如下:
# tests/test_extractor/test_api.py
import pytest
from example_etl.extractor.api import ApiExtractor
def test_api_extractor_success(mocker):
# 1. 准备模拟数据
mock_config = mocker.MagicMock()
mock_config.API_URL = "https://api.example.com/data"
expected_data = {"key": "value"}
# 2. Mock掉requests.get
mock_response = mocker.MagicMock()
mock_response.json.return_value = expected_data
mock_response.raise_for_status = mocker.MagicMock() # 这个方法不应该做任何事
mock_get = mocker.patch('example_etl.extractor.api.requests.get', return_value=mock_response)
# 3. 执行测试
extractor = ApiExtractor(mock_config)
result = extractor.extract()
# 4. 验证行为
# 4.1 验证调用了正确的URL
mock_get.assert_called_once_with("https://api.example.com/data")
# 4.2 验证返回了正确的数据
assert result == expected_data
# 4.3 验证raise_for_status被调用(确保错误处理逻辑被触发)
mock_response.raise_for_status.assert_called_once()
def test_api_extractor_failure(mocker):
mock_config = mocker.MagicMock()
mock_config.API_URL = "https://api.example.com/data"
# Mock一个会抛出异常的requests.get
mock_get = mocker.patch('example_etl.extractor.api.requests.get')
mock_get.side_effect = requests.exceptions.ConnectionError("Network error")
extractor = ApiExtractor(mock_config)
# 断言异常被正确抛出(从extract方法中抛出)
with pytest.raises(requests.exceptions.ConnectionError):
extractor.extract()
Mock的核心技巧:
-
mocker.patch:用于替换指定命名空间下的对象。关键是要patch在 被测试代码使用它的地方 。这里我们patch的是'example_etl.extractor.api.requests.get',而不是'requests.get',因为前者是测试模块中导入的路径。 -
return_value与side_effect:return_value让被mock的函数/方法返回一个固定值。side_effect更强大,可以是一个异常(用于测试错误处理)、一个可迭代对象(每次调用返回下一个值)或一个函数(自定义返回值逻辑)。 - 行为断言 :使用
assert_called_once_with,assert_called_with,assert_not_called等方法来验证mock对象是否被以预期的方式调用。这是Mock测试的精髓——验证模块间的交互协议。
4.3 组织集成测试
单元测试之上是集成测试,用于测试多个模块协同工作是否正常。例如,测试整个ETL管道的 Manage 类。
# tests/test_integration/test_manage.py
import pytest
from unittest.mock import Mock, call
from example_etl.manage import Manage
from example_etl.extractor.file import FileExtractor
from example_etl.transformer.strip import StripTransformer
from example_etl.loader.file import FileLoader
def test_full_etl_pipeline_integration(mocker, tmp_path):
# 1. 准备真实的测试文件和目录(使用pytest内置的tmp_path fixture)
input_file = tmp_path / "input.txt"
input_file.write_text(" a \n b \n c ")
output_file = tmp_path / "output.txt"
# 2. 创建真实的组件实例,但使用Mock配置
mock_config = mocker.MagicMock()
mock_config.FILE_EXTRACTOR_PATH = str(input_file)
mock_config.FILE_LOADER_PATH = str(output_file)
# 3. 实例化管理器并运行
# 注意:这里我们可能选择不mock内部组件,让它们真实交互
# 但为了测试的纯粹性,我们也可以mock掉组件的具体实现,只测试流程
# 这里演示一个更偏向“集成”的测试:mock掉插件发现,但使用真实的转换逻辑
mock_get_extractor = mocker.patch('example_etl.manage.get_extension')
mock_get_transformer = mocker.patch('example_etl.manage.get_extension')
mock_get_loader = mocker.patch('example_etl.manage.get_extension')
mock_get_extractor.return_value = FileExtractor
mock_get_transformer.return_value = StripTransformer
mock_get_loader.return_value = FileLoader
manager = Manage(config=mock_config)
manager.run()
# 4. 验证结果:输出文件内容应该被去除空格
assert output_file.read_text() == "a\nb\nc"
# 验证插件获取函数被以正确的参数调用
mock_get_extractor.assert_called_once_with('example_etl.extractor', 'file')
集成测试的边界需要根据项目情况仔细界定。它比单元测试慢,也更脆弱(因为依赖更多真实组件),但能发现单元测试发现不了的模块间接口问题。通常建议将集成测试用 @pytest.mark.integration 标记,以便在快速开发循环中可以选择性跳过。
5. 高级技巧、配置与持续集成实践
当基础测试稳定运行后,我们需要引入更多工程化实践来提升整个测试套件的质量和效率。
5.1 测试覆盖率报告与阈值
pytest-cov 不仅可以生成报告,还可以设置覆盖率阈值,如果未达到则令测试失败,这在CI/CD流水线中非常有用。
# 运行测试并生成终端报告
pytest --cov=src/example_etl --cov-report=term-missing
# 生成HTML报告,便于详细查看
pytest --cov=src/example_etl --cov-report=html
在 pyproject.toml 中配置覆盖率阈值和报告格式:
[tool.coverage.run]
source = ["src/example_etl"] # 指定要统计覆盖率的源代码目录
omit = ["*/__pycache__/*", "*test*"] # 忽略的路径
[tool.coverage.report]
# 设置各覆盖率类型的失败阈值
fail_under = 90 # 总覆盖率低于90%则失败
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if self.debug:",
"if settings.DEBUG",
"logger\\.debug",
]
5.2 使用pytest插件增强功能
pytest 生态丰富,有许多插件可以解决特定问题:
-
pytest-xdist: 并行运行测试,大幅缩短测试时间。pytest -n auto(auto表示使用所有CPU核心)。 -
pytest-django/pytest-flask: 专门为Django/Flask框架测试提供fixture和工具。 -
pytest-asyncio: 用于测试异步asyncio代码。 -
pytest-bdd: 支持行为驱动开发(BDD),用Gherkin语法写测试。 -
pytest-timeout: 为测试设置超时,防止某些测试卡死。
5.3 利用tox实现多环境自动化测试
tox 是一个虚拟环境管理和测试命令行工具。它可以为你的项目在多个Python版本、多个依赖配置下自动运行测试套件。这对于确保库的兼容性至关重要。
一个基础的 tox.ini 配置:
# tox.ini
[tox]
envlist = py310, py311, py312, lint, type-check
isolated_build = true
[testenv]
# 所有测试环境共享的命令
deps =
pytest
pytest-mock
pytest-cov
pytest-xdist
commands =
pytest -v --cov=src/example_etl --cov-report=term-missing --junitxml=junit-{envname}.xml -n auto tests/
[testenv:lint]
# 代码风格检查环境
deps =
black
isort
flake8
pylint
commands =
black --check --diff src tests
isort --check-only --diff src tests
flake8 src tests
pylint src tests --fail-under=8.0
[testenv:type-check]
# 类型检查环境(如果使用mypy)
deps =
mypy
commands =
mypy src
运行 tox ,它会依次为 py310 , py311 , py312 创建虚拟环境,安装依赖并运行测试,然后再运行 lint 和 type-check 环境。 --junitxml 参数生成JUnit格式的报告,方便CI系统(如Jenkins, GitLab CI)集成展示。
5.4 集成到Git钩子与CI/CD
为了在代码提交前就发现问题,可以将测试和代码检查集成到Git的 pre-commit 钩子中。使用 pre-commit 框架可以方便地管理多种钩子。
.pre-commit-config.yaml 示例:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
args: [--safe]
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black"]
- repo: local
hooks:
- id: pytest-check
name: Run fast tests
entry: poetry run pytest -m "not slow and not integration"
language: system
pass_filenames: false
always_run: true
stages: [commit]
在CI/CD流水线(如GitHub Actions, GitLab CI)中,配置一个任务来运行完整的 tox 或 pytest 套件,确保合并到主分支的代码始终是健康的。
6. 常见问题、调试技巧与性能优化
即使有了完善的指南,在实际操作中还是会遇到各种问题。这里记录了一些我踩过的坑和总结的技巧。
6.1 测试执行缓慢怎么办?
测试慢是开发效率的杀手。原因和解决方案:
- 原因1:过多的I/O或网络调用。 这是最常见的原因。 解决方案 :坚决使用Mock。对于数据库,使用内存数据库(如SQLite
:memory:)或专门的测试数据库,并在fixture中做好数据准备和清理。对于网络请求,必须Mock。 - 原因2:测试套件庞大,但每次运行全部。 解决方案 :
- 使用
pytest-xdist并行运行 :pytest -n auto。 - 只运行修改相关的测试 :使用
pytest-picked插件或pytest --lf(只运行上次失败的)和pytest --new-first(先运行新加的)。 - 按标记(mark)选择运行 :用
@pytest.mark.slow标记耗时测试,日常使用pytest -m "not slow"跳过。
- 使用
- 原因3:测试启动环境慢。 如果项目依赖庞大(如科学计算库),每次创建虚拟环境都很慢。 解决方案 :在CI中可以利用缓存(cache)来复用
pip下载的包。本地开发可以考虑使用pip的--user安装部分重型依赖,或者使用Docker预构建的镜像。
6.2 测试时遇到ImportError或ModuleNotFoundError
这通常是由于Python路径( sys.path )问题引起的,尤其是在使用 src 目录布局时。
- 确保以可编辑模式安装你的包 :在项目根目录运行
pip install -e .或poetry install。这会将你的包链接到当前环境,使其可被导入。 - 在
pytest配置中设置pythonpath:在pyproject.toml中增加pythonpath = ["src"],告诉pytest将src目录加入sys.path。 - 检查
conftest.py的位置 :conftest.py中的fixture和hook只在它所在目录及其子目录中生效。确保你的conftest.py放在正确的位置(通常是tests/根目录)。
6.3 Mock不生效?注意Patch的位置
这是Mock中最容易出错的地方。你必须 patch 在 被测试对象看到的地方 。
- 错误示例 :你在
test_module.py中import requests,然后mocker.patch('requests.get')。但被测试文件my_module.py是从另一个地方导入的requests(例如from urllib import request),那么你的patch就无效。 - 黄金法则 :总是
patch目标模块内部的引用。查看被测试函数的源代码,找到它import语句的位置,然后patch那个完整的路径。使用print(mocker.patch.object)或调试器查看patch的目标是否正确。
6.4 如何测试异步(asyncio)代码?
使用 pytest-asyncio 插件。你需要用 @pytest.mark.asyncio 标记你的异步测试函数,并在其中使用 await 。
import pytest
import asyncio
from myapp.async_module import async_fetch
@pytest.mark.asyncio
async def test_async_fetch(mocker):
mock_session = mocker.AsyncMock() # 注意使用AsyncMock
mock_session.get.return_value.__aenter__.return_value.json = mocker.AsyncMock(return_value={'data': 'test'})
result = await async_fetch(mock_session, 'http://example.com')
assert result == {'data': 'test'}
6.5 测试随机失败(Flaky Tests)
这是最令人头疼的问题之一。测试大部分时间通过,但偶尔失败。原因通常与状态残留、时间依赖、并发竞争有关。
- 排查步骤 :
- 隔离测试 :单独运行失败的测试,看是否稳定复现。
- 检查fixture作用域 :默认
fixture是function作用域,每次测试都会新建。如果错误地使用了session或module作用域,可能导致状态污染。 - 检查时间/随机性 :测试中是否使用了
time.sleep()、datetime.now()或random?用mocker.patch替换它们(如mocker.patch('time.sleep'),mocker.patch('datetime.datetime.now', return_value=fixed_time))。 - 检查外部依赖 :即使Mock了,也要确保Mock的行为是确定性的。
side_effect列表是否用完了?return_value是否每次都返回新对象(避免可变对象被修改)? - 使用
pytest-flakefinder:这个插件可以让你重复运行测试多次,以暴露不稳定的测试。
- 根本解决 :为Flaky Test添加
@pytest.mark.flaky(reruns=3)标记(需要pytest-rerunfailures插件),让它失败时自动重试几次。但这只是权宜之计,最终还是要找到并修复其不稳定的根源。
6.6 测试数据库操作
测试数据库时,核心原则是 测试之间完全隔离 。
- 使用内存数据库 :如SQLite
:memory:。速度极快,且天然隔离。 - 使用事务回滚 :对于不支持内存模式的数据库(如PostgreSQL),可以在每个测试开始时开启一个事务,测试结束后回滚。
pytest-django、pytest-sqlalchemy等插件提供了这样的fixture。 - 使用独立的测试数据库 :在CI环境中,可以创建一个专用于测试的数据库,并在每次测试运行前用
alembic(迁移工具)或原始SQL重置其结构。 - 用fixture准备测试数据 :在
fixture中创建测试所需的初始数据,并确保fixture是function作用域,这样每个测试得到的数据都是全新的。
# 使用pytest + SQLAlchemy + 内存SQLite的示例
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from myapp.models import Base, User
@pytest.fixture(scope="session")
def engine():
"""创建内存数据库引擎,作用域为session(整个测试过程一次)。"""
return create_engine("sqlite:///:memory:")
@pytest.fixture(scope="session")
def tables(engine):
"""创建所有表,作用域为session。"""
Base.metadata.create_all(engine)
yield
Base.metadata.drop_all(engine)
@pytest.fixture
def db_session(engine, tables):
"""为每个测试函数提供一个独立的数据库会话,并在测试后回滚。"""
connection = engine.connect()
transaction = connection.begin()
Session = sessionmaker(bind=connection)
session = Session()
yield session
session.close()
transaction.rollback()
connection.close()
def test_create_user(db_session):
user = User(name="Alice")
db_session.add(user)
db_session.commit()
fetched_user = db_session.query(User).filter_by(name="Alice").first()
assert fetched_user is not None
assert fetched_user.name == "Alice"
构建一个专业的pytest测试项目,远不止是学会写 assert 语句。它是一个系统工程,涉及项目结构设计、依赖管理、测试策略(单元、集成)、Mock技术、覆盖率管理、代码风格、以及自动化流水线。其最终目的,是建立一个快速、可靠、可维护的安全网,让你在修改代码时充满信心,在重构系统时步履稳健,在交付产品时心中有底。这套实践并非一成不变,你需要根据项目的规模、团队的习惯和技术的演进不断调整。但万变不离其宗的核心,始终是那几条:隔离、重复、快速、清晰。当你把这些原则融入到日常的测试编写中时,你会发现,写测试不再是一项枯燥的负担,而是驱动你设计出更好代码的催化剂。
更多推荐

所有评论(0)