Python 代码质量工具链——pytest + coverage + pylint + black 一键配置
2026/4/30大约 6 分钟
Python 代码质量工具链——pytest + coverage + pylint + black 一键配置
适读人群:Python 开发者 / 关注代码质量的团队 | 阅读时长:约 14 分钟 | 核心价值:从零配置一套完整的 Python 代码质量保障工具链,一次配置终身受益
新同事提交的代码让我头皮发麻
新来的实习生小陈提交了第一个 PR,我 Code Review 时看到这样的代码:
def calc(x,y,op):
if op=='add':
return x+y
elif op=='sub':
return x-y
elif op=='mul':
return x*y
else:
return x/y # 没有处理除以零没有类型注解,变量名不规范,没有文档,没有错误处理,格式乱。
我花了 20 分钟写了很长的 Code Review 意见。
然后我意识到这是浪费——这些格式问题完全可以让工具自动发现和修复。我应该做的是配置好工具链,让工具替我做这些机械的检查,我的 Code Review 精力只花在逻辑和架构上。
工具链全景
pytest → 测试运行
coverage → 代码覆盖率(配合 pytest-cov)
black → 代码格式化(自动修复)
isort → import 排序(自动修复)
pylint → 代码质量检查(静态分析)
mypy → 类型检查(静态分析)
pre-commit → Git 提交前自动运行所有检查安装
pip install pytest pytest-cov coverage black isort pylint mypy pre-commit或者用 pyproject.toml(现代推荐方式):
# pyproject.toml
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
[project.optional-dependencies]
test = [
"pytest>=7.4",
"pytest-cov>=4.1",
"pytest-asyncio>=0.23",
"pytest-mock>=3.12",
]
dev = [
"black>=23.12",
"isort>=5.13",
"pylint>=3.0",
"mypy>=1.8",
"pre-commit>=3.6",
]配置文件:pyproject.toml 一站式管理
把所有工具的配置集中在 pyproject.toml 里:
# pyproject.toml
# ==================== pytest 配置 ====================
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers", # 未注册的 marker 报错
"--strict-config", # 配置错误报错
"-ra", # 显示所有非通过的测试摘要
"--tb=short", # 简短的失败信息
]
markers = [
"slow: 慢速测试,默认跳过",
"integration: 集成测试,需要数据库",
"smoke: 冒烟测试",
"e2e: 端到端测试",
]
asyncio_mode = "auto"
# ==================== coverage 配置 ====================
[tool.coverage.run]
source = ["myapp"] # 只统计 myapp 目录的覆盖率
omit = [
"*/migrations/*",
"*/tests/*",
"*/conftest.py",
"*/__pycache__/*",
"*/venv/*",
]
branch = true # 开启分支覆盖率(比行覆盖率更严格)
[tool.coverage.report]
show_missing = true # 显示未覆盖的行号
skip_covered = false # 显示 100% 覆盖的文件
fail_under = 80 # 覆盖率低于 80% 时失败
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
[tool.coverage.html]
directory = "htmlcov"
# ==================== black 配置 ====================
[tool.black]
line-length = 100
target-version = ["py311"]
include = '\.pyi?$'
exclude = '''
/(
migrations
| venv
| .git
| __pycache__
)/
'''
# ==================== isort 配置 ====================
[tool.isort]
profile = "black" # 和 black 兼容的配置
line_length = 100
known_first_party = ["myapp"]
known_third_party = ["fastapi", "sqlalchemy", "pydantic", "pytest"]
# ==================== mypy 配置 ====================
[tool.mypy]
python_version = "3.11"
strict = false # 先不用严格模式,逐步迁移
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false # 测试文件不要求类型注解pytest + coverage 配置和使用
# 运行测试并生成覆盖率报告
pytest --cov=myapp --cov-report=html --cov-report=term-missing
# 只运行特定标记的测试
pytest -m smoke
pytest -m "not slow"
# 并行运行(需要 pytest-xdist)
pytest -n auto
# 完整 CI 命令
pytest \
--cov=myapp \
--cov-report=term-missing \
--cov-report=html:htmlcov \
--cov-report=xml:coverage.xml \
--cov-fail-under=80 \
-v覆盖率报告示例输出:
Name Stmts Miss Branch BrPart Cover Missing
-------------------------------------------------------------------------
myapp/services/user.py 42 4 12 2 89% 45-48, 62->64
myapp/services/payment.py 87 18 24 4 78% 120-135
-------------------------------------------------------------------------
TOTAL 129 22 36 6 84%pylint 配置
# .pylintrc 或在 pyproject.toml 中:
[tool.pylint.main]
ignore = ["migrations", "venv"]
ignore-patterns = ["^\\.#"]
[tool.pylint.messages_control]
disable = [
"missing-docstring", # 不强制要求 docstring(可以逐步启用)
"too-few-public-methods",
"too-many-arguments", # 可以逐步收紧
"too-many-instance-attributes",
"import-error", # 交给 mypy 处理
]
[tool.pylint.format]
max-line-length = 100
[tool.pylint.design]
max-args = 8
max-locals = 15
max-returns = 6常用检查项:
# 检查单个文件
pylint myapp/services/user.py
# 检查整个包
pylint myapp/
# 只显示错误和警告,忽略提示
pylint --disable=C,R myapp/
# 生成 JSON 报告
pylint myapp/ --output-format=json > pylint-report.jsonpre-commit:提交前自动检查
pre-commit 在 git commit 之前自动运行所有检查,确保不合格的代码无法提交:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace # 去除行尾空格
- id: end-of-file-fixer # 确保文件以换行结尾
- id: check-yaml # 检查 YAML 格式
- id: check-json # 检查 JSON 格式
- id: check-merge-conflict # 检查是否有未解决的合并冲突
- id: debug-statements # 检查是否有 print/pdb 语句
- id: check-added-large-files # 防止提交大文件
args: ["--maxkb=500"]
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/pylint-dev/pylint
rev: v3.0.3
hooks:
- id: pylint
args: ["--disable=C,R"] # CI 阶段只检查错误和警告
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: ["pydantic>=2", "types-requests"]安装和使用:
# 初始化 pre-commit(克隆项目后执行一次)
pre-commit install
# 手动在所有文件上运行
pre-commit run --all-files
# 运行特定 hook
pre-commit run black --all-files配置后,每次 git commit 时会自动运行:
Check for trailing whitespace.............................Passed
Fix End of Files..........................................Passed
Check Yaml................................................Passed
black.....................................................Failed
- hook id: black
- files were modified by this hook
reformatted myapp/services/user.py
All done! ✨ 🍰 ✨
1 file reformattedblack 直接修改了文件,重新 git add 后提交即可。
Makefile:一键命令
# Makefile
.PHONY: test lint format check ci-check
# 运行所有测试
test:
pytest tests/ -v
# 运行测试 + 覆盖率
test-cov:
pytest tests/ --cov=myapp --cov-report=term-missing --cov-report=html -v
# 只运行快速测试(跳过 slow 和 e2e)
test-fast:
pytest tests/ -m "not slow and not e2e" -v
# 代码格式化(自动修复)
format:
black myapp/ tests/
isort myapp/ tests/
# 代码检查(不修改)
lint:
black --check myapp/ tests/
isort --check-only myapp/ tests/
pylint myapp/
mypy myapp/
# 运行所有检查(CI 使用)
ci-check: lint
pytest tests/ --cov=myapp --cov-fail-under=80 -v
# 清理生成文件
clean:
rm -rf .pytest_cache htmlcov .coverage coverage.xml
find . -type d -name __pycache__ -exec rm -rf {} +踩坑实录
坑一:black 和 isort 格式冲突
现象: black 格式化完,isort 再改一遍,两个工具的格式不兼容。
解法: 在 isort 配置中设置 profile = "black",让 isort 使用和 black 兼容的格式。
坑二:pylint 报告太多误报
现象: pylint 有大量 C(Convention)警告,很多是合理的代码被标记为问题。
解法: 渐进式启用:
# 第一阶段:只看错误(E)
pylint --disable=W,C,R myapp/
# 第二阶段:加入警告(W)
pylint --disable=C,R myapp/
# 第三阶段:全面检查
pylint myapp/坑三:pre-commit 在 CI 上运行慢
现象: pre-commit 在 CI 每次都要下载 hook 依赖,很慢。
解法: 在 CI 中缓存 pre-commit 的 cache 目录:
# GitHub Actions
- uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}小结
Python 代码质量工具链的推荐配置:
| 工具 | 作用 | 是否自动修复 |
|---|---|---|
| black | 代码格式化 | 是 |
| isort | import 排序 | 是 |
| pylint | 静态分析 | 否(提报告) |
| mypy | 类型检查 | 否(提报告) |
| pytest-cov | 覆盖率统计 | 否(提报告) |
| pre-commit | 提交前门卫 | 调用以上工具 |
一次配置好,让工具自动守护代码质量,你的 Code Review 时间专注在真正重要的地方。
