Python项目结构和pytest

Python项目结构和pytest

Last updated on


1064 字 约 6 分钟

1.一个Python项目通常的组成

现代 Python 项目通常不是只有 .py 文件,而是由以下几层组成:

Text
backend/
├── app/                  # 业务代码,也就是应用本体
│   ├── __init__.py
│   ├── main.py           # FastAPI 入口
│   └── ...
├── tests/                # 测试代码
│   └── test_health.py
├── requirements.txt      # 依赖清单,告诉 pip 要安装哪些第三方包
├── pyproject.toml        # 项目元信息 + 工具配置
├── .env.example          # 环境变量示例,可以提交 Git
└── .env                  # 本机真实环境变量,不应提交 Git

从 Python 的视角,可以分成五层:

层级作用本项目示例
代码层真正的业务代码backend/app/
测试层验证代码是否正确backend/tests/
依赖层声明项目依赖哪些包requirements.txt
工具配置层配置测试、格式化、Python版本pyproject.toml
运行环境层环境变量、虚拟环境、数据库等.env.venv
  • 核心心智模型:
    • requirements.txt管”安装什么包”
    • pyproject.toml管”项目和工具怎么工作”
    • pytest管”怎么验证代码是否正确”

2.pyproject.toml是什么

pyproject.toml 是现代 Python 项目的项目配置中心。

它可以配置如下内容

  • 项目名称、版本、Python 版本要求;
  • pytest 测试行为;
  • ruff 代码检查规则;
  • black 格式化规则;
  • mypy 类型检查规则;
  • poetry、hatch、uv 等构建或包管理工具。

以我当前的一个项目的配置为例:

TOML
[project]
name = "multi-agent-algorithmic-arena-backend"
version = "0.1.0"
description = "FastAPI backend for Multi-Agent Algorithmic Arena"
requires-python = ">=3.11"

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"

[tool.ruff]
line-length = 100
target-version = "py311"

2.1.[project]:项目元信息

TOML
[project]
name = "multi-agent-algorithmic-arena-backend"
version = "0.1.0"
description = "FastAPI backend for Multi-Agent Algorithmic Arena"
requires-python = ">=3.11"

这一段用于描述这个Python项目本身

  • name:项目名
  • version:项目版本,常见格式为语义化版本major.minor.patch
  • description:描述
  • requires-python:表示本项目要求的Python版本

2.2.[tool.pytest]:pytest配置

TOML
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"

这一段告诉 pytest:运行测试时应该如何找测试、如何处理导入、如何处理异步测试

  • testpaths = ["tests"]:表示pytest默认只在tests/目录下查找测试
  • pythonpath = ["."]:等价于告诉pytest,运行测试时把 backend/ 加入Python的模块搜索路径
  • asyncio mode = "auto":FastAPI会大量使用异步代码,pytest本身需要配合pytest-asyncio才能方便地运行异步测试
  • asyncio_default_fiture_loop_scope = "function":表示异步测试的fixture默认使用函数级别作用域

2.3.[tool.ruff]:代码检查配置

TOML
[tool.ruff]
line-length = 100
target-version = "py311"

Ruff 是一个很快的 Python 代码检查和格式化工具,可以检查未使用的import,变量命名问题,代码风格问题等等

3.pyproject.tomlrequirements.txt 的区别

文件主要作用主要给谁用
requirements.txt依赖安装清单pip
pyproject.toml项目和工具配置pytest、ruff、构建工具等

对于requirements.txt

Text
fastapi==0.115.6
uvicorn[standard]==0.34.0
sqlalchemy[asyncio]==2.0.36
aiosqlite==0.20.0
alembic==1.14.0
pydantic-settings==2.7.1
python-dotenv==1.0.1
pytest==8.3.4
pytest-asyncio==0.25.2
httpx==0.28.1

它回答的问题是:

  • 这个项目运行和测试需要安装哪些第三方库?

执行:

Bash
pip install -r requirements.txt

pip 就会根据这个文件安装依赖

pyproject.toml 回答的是:

  • 这个项目叫什么?
  • 要求什么 Python 版本?
  • pytest 怎么运行?
  • ruff 怎么检查?
  • 构建工具怎么工作?

4.pytest是什么

pytest 是 Python 最常用的测试框架之一,它的作用是自动发现测试文件,运行测试函数,报告哪些通过、哪些失败

4.1.测试文件

Python
from fastapi.testclient import TestClient

from app.main import app


client = TestClient(app)


def test_root_returns_api_message() -> None:
    response = client.get("/")

    assert response.status_code == 200
    assert response.json() == {"message": "Multi-Agent Algorithmic Arena API"}


def test_health_check_returns_ok() -> None:
    response = client.get("/health")

    assert response.status_code == 200
    assert response.json() == {"status": "ok"}

4.2.如何查找

pytest 默认会查找:

Text
test_*.py
*_test.py

并运行其中以 test_ 开头的函数

这两个函数会被运行:

Python
def test_root_returns_api_message() -> None:
    ...


def test_health_check_returns_ok() -> None:
    ...

4.3.pytest 如何判断测试是否通过

pytest 主要看 assert,例如:

Python
assert response.status_code == 200

如果实际结果是 200,测试通过。

如果实际结果是 404,pytest 会报告失败,并告诉你类似于

Text
assert 404 == 200

评论

评论会在审核后展示

正在加载人机验证...