FastAPI测试策略:参数解析单元测试

扫描二维码关注或者微信搜一搜:编程智域 前端至全栈交流与成长

探索数千个预构建的 AI 应用,开启你的下一个伟大创意


第一章:核心测试方法论

1.1 三层测试体系架构

python 复制代码
# 第一层:模型级测试
def test_user_model_validation():
    with pytest.raises(ValidationError):
        User(age=-5)


# 第二层:依赖项测试
def test_auth_dependency():
    assert auth_dependency(valid_token).status == "active"


# 第三层:端点集成测试
def test_user_endpoint():
    response = client.get("/users/1")
    assert response.json()["id"] == 1

1.2 参数化测试模式

python 复制代码
import pytest


@pytest.mark.parametrize("input,expected", [
    ("admin", 200),
    ("guest", 403),
    ("invalid", 401)
])
def test_role_based_access(input, expected):
    response = client.get(
        "/admin",
        headers={"X-Role": input}
    )
    assert response.status_code == expected

第二章:请求模拟技术

2.1 多协议请求构造

python 复制代码
from fastapi.testclient import TestClient


def test_multi_part_form():
    response = TestClient(app).post(
        "/upload",
        files={"file": ("test.txt", b"content")},
        data={"name": "test"}
    )
    assert response.status_code == 201


def test_graphql_query():
    response = client.post(
        "/graphql",
        json={"query": "query { user(id:1) { name } }"}
    )
    assert "errors" not in response.json()

2.2 动态Header注入

python 复制代码
class AuthTestClient(TestClient):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.token = generate_test_token()

    def get(self, url, **kwargs):
        headers = kwargs.setdefault("headers", {})
        headers.setdefault("Authorization", f"Bearer {self.token}")
        return super().get(url, **kwargs)


test_client = AuthTestClient(app)

第三章:Pydantic深度测试

3.1 自定义验证器测试

python 复制代码
def test_custom_validator():
    with pytest.raises(ValidationError) as excinfo:
        Product(stock=-10)

    assert "库存不能为负" in str(excinfo.value)


def test_regex_validation():
    valid = {"email": "[email protected]"}
    invalid = {"email": "invalid-email"}

    assert EmailRequest(**valid)
    with pytest.raises(ValidationError):
        EmailRequest(**invalid)

3.2 模型继承测试

python 复制代码
class BaseUserTest:
    @pytest.fixture
    def model_class(self):
        return BaseUser


class TestAdminUser(BaseUserTest):
    @pytest.fixture
    def model_class(self):
        return AdminUser

    def test_admin_privilege(self, model_class):
        user = model_class(role="super_admin")
        assert user.has_privilege("all")

第四章:测试覆盖率优化

4.1 边界条件覆盖策略

python 复制代码
# 使用hypothesis生成测试数据
from hypothesis import given, strategies as st


@given(st.integers(min_value=0, max_value=150))
def test_age_validation(age):
    assert 0 <= User(age=age).age <= 120


@given(st.text(min_size=1, max_size=50))
def test_username_validation(name):
    if not name.isalnum():
        with pytest.raises(ValidationError):
            User(username=name)
    else:
        assert User(username=name)

4.2 依赖覆盖测试

python 复制代码
def test_external_service_override():
    mock_service = MockExternalService()

    app.dependency_overrides[get_external_service] = lambda: mock_service

    response = client.get("/data")
    assert response.json() == mock_service.expected_data

    app.dependency_overrides = {}

第五章:异常处理测试

5.1 错误传播验证

python 复制代码
def test_error_chain():
    with pytest.raises(HTTPException) as excinfo:
        client.get("/error-path")

    exc = excinfo.value
    assert exc.status_code == 500
    assert "原始错误" in exc.detail


def test_validation_error_format():
    response = client.post("/users", json={"age": "invalid"})
    assert response.status_code == 422
    assert response.json()["detail"][0]["type"] == "type_error.integer"

5.2 压力测试场景

python 复制代码
def test_concurrent_requests():
    with ThreadPoolExecutor() as executor:
        futures = [
            executor.submit(
                client.get,
                f"/items/{i}"
            ) for i in range(1000)
        ]
        results = [f.result().status_code for f in futures]

    assert all(code == 200 for code in results)

课后Quiz

Q1:如何测试需要认证的端点?

A) 直接访问无需处理

B) 使用自定义TestClient注入Header

C) 关闭服务端认证

Q2:参数化测试的主要作用是?

  1. 减少测试代码量
  2. 覆盖多种边界条件
  3. 提高单个测试速度

Q3:如何验证自定义验证器?

  • 主动触发验证错误
  • 跳过模型测试
  • 仅测试成功案例

错误解决方案速查表

测试错误类型 解决方案
依赖项初始化失败 检查测试依赖覆盖是否正确定义
验证错误未触发 确认测试数据包含非法边界值
异步断言失败 使用pytest-asyncio管理异步测试
临时文件残留 使用tmp_path夹具自动清理

扩展工具推荐

  1. pytest-cov - 测试覆盖率分析
  2. Hypothesis - 基于属性的测试框架
  3. responses - 外部请求模拟库
  4. factory_boy - 测试数据工厂

测试箴言:优秀的测试体系应遵循测试金字塔原则,单元测试占比不低于70%。建议采用Given-When-Then模式编写测试用例,保持单个测试的原子性,使用突变测试检测测试有效性,并定期进行测试代码重构。

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:

往期文章归档: