商道如狼道 发表于 4 天前

MonkeyCode写单位测试:从零到高覆盖率

不写测试的代码是负债。MonkeyCode帮你把测试覆盖率从0%提到80%+,而且写起来不痛楚。
为什么步伐员不写测试?

三个字:懒、烦、慢。

[*]写测试的时间比写业务代码还长
[*]改一行业务代码,要改10个测试
[*]不知道测什么、怎么Mock、怎么断言
MonkeyCode把这三点全办理了。
快速上手:让MonkeyCode天生测试

假设你有如许一个业务函数:
# app/order_service.py
import db

async def create_order(user_id: int, items: list, shipping_address: str) -> dict:
    """创建订单"""
    # 验证用户
    user = await db.fetch_one("SELECT * FROM users WHERE id = ?", user_id)
    if not user:
      raise ValueError("用户不存在")
   
    # 验证库存
    for item in items:
      inv = await db.fetch_one("SELECT stock FROM inventory WHERE sku = ?", item["sku"])
      if not inv or inv["stock"] < item["qty"]:
            raise ValueError(f"库存不足: {item['sku']}")
   
    # 计算总价
    total = sum(item["price"] * item["qty"] for item in items)
   
    # 创建订单
    order_id = await db.execute(
      "INSERT INTO orders (user_id, total_amount, address) VALUES (?, ?, ?)",
      (user_id, total, shipping_address)
    )
   
    # 扣库存
    for item in items:
      await db.execute(
            "UPDATE inventory SET stock = stock - ? WHERE sku = ?",
            (item["qty"], item["sku"])
      )
   
    return {"order_id": order_id, "total": total}直接对MonkeyCode说:
给 create_order 函数写完整的pytest单元测试,包含:
1. 正常流程测试
2. 用户不存在的异常测试
3. 库存不足的异常测试
4. Mock数据库依赖(用pytest-mock)
5. 参数化测试(多组输入)
6. 覆盖率100%MonkeyCode天生:
# tests/test_order_service.py
import pytest
from unittest.mock import AsyncMock, MagicMock
import sys
sys.path.insert(0, ".")

from app.order_service import create_order

# ─── 正常流程 ───
@pytest.mark.asyncio
async def test_create_order_success(mocker):
    """测试正常创建订单"""
    # Mock db
    mock_db = mocker.patch("app.order_service.db")
   
    # Mock用户查询
    mock_db.fetch_one.side_effect = [
      {"id": 1, "name": "张三"},# 用户存在
      {"stock": 100},                  # 库存充足
    ]
   
    # Mock写入
    mock_db.execute.return_value = 10086
   
    result = await create_order(
      user_id=1,
      items=[{"sku": "SKU001", "qty": 2, "price": 50}],
      shipping_address="北京市朝阳区"
    )
   
    assert result["order_id"] == 10086
    assert result["total"] == 100# 50 * 2
    mock_db.execute.assert_called_once()

# ─── 异常:用户不存在 ───
@pytest.mark.asyncio
async def test_create_order_user_not_found(mocker):
    mock_db = mocker.patch("app.order_service.db")
    mock_db.fetch_one.return_value = None# 用户不存在
   
    with pytest.raises(ValueError, match="用户不存在"):
      await create_order(1, [], "")

# ─── 异常:库存不足 ───
@pytest.mark.asyncio
async def test_create_order_insufficient_stock(mocker):
    mock_db = mocker.patch("app.order_service.db")
    mock_db.fetch_one.side_effect = [
      {"id": 1, "name": "张三"},   # 用户存在
      None,                           # 库存记录不存在
    ]
   
    with pytest.raises(ValueError, match="库存不足"):
      await create_order(1, [{"sku": "SKU001", "qty": 1, "price": 50}], "")

# ─── 参数化测试:多组输入 ───
@pytest.mark.parametrize("items,expected_total", [
    ([{"sku": "A", "qty": 1, "price": 100}], 100),
    ([{"sku": "A", "qty": 3, "price": 50}], 150),
    ([{"sku": "A", "qty": 2, "price": 10}, {"sku": "B", "qty": 1, "price": 80}], 100),
])
@pytest.mark.asyncio
async def test_create_order_total_calculation(mocker, items, expected_total):
    mock_db = mocker.patch("app.order_service.db")
    mock_db.fetch_one.side_effect = [
      {"id": 1},
      {"stock": 999},
    ]
    mock_db.execute.return_value = 1
   
    result = await create_order(1, items, "addr")
    assert result["total"] == expected_total

# ─── Mock调用次数验证 ───
@pytest.mark.asyncio
async def test_create_order_db_calls(mocker):
    """验证数据库调用次数和参数"""
    mock_db = mocker.patch("app.order_service.db")
    mock_db.fetch_one.side_effect = [{"id": 1}, {"stock": 100}]
    mock_db.execute.return_value = 1
   
    await create_order(1, [{"sku": "A", "qty": 1, "price": 10}], "addr")
   
    # 验证:1次用户查询 + 1次库存查询 + 1次写入 + 1次扣库存
    assert mock_db.fetch_one.call_count == 2
    assert mock_db.execute.call_count == 2运行测试 + 覆盖率陈诉

pip install pytest pytest-mock pytest-cov

# 运行测试
pytest tests/ -v

# 覆盖率报告
pytest tests/ --cov=app --cov-report=html --cov-report=term

# 查看HTML报告
open htmlcov/index.htmlFastAPI接口的测试

# tests/test_api.py
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_create_order_api(mocker):
    # Mock业务函数
    mocker.patch("app.order_service.create_order", return_value={"order_id": 1, "total": 100})
   
    resp = client.post("/orders", json={
      "user_id": 1,
      "items": [{"sku": "A", "qty": 1, "price": 100}],
      "shipping_address": "北京"
    }, headers={"Authorization": "Bearer faketoken"})
   
    assert resp.status_code == 200
    assert resp.json()["order_id"] == 1

def test_create_order_api_unauthorized():
    resp = client.post("/orders", json={})
    assert resp.status_code == 401# 未授权数据库测试的夹具(Fixture)

# tests/conftest.py
import pytest
import asyncio
from app.db import get_db

@pytest.fixture
def event_loop():
    """创建事件循环,供所有异步测试使用"""
    loop = asyncio.new_event_loop()
    yield loop
    loop.close()

@pytest.fixture
async def db_session():
    """创建测试数据库会话(事务回滚,不污染真实数据)"""
    async with TestTransaction() as tx:
      yield tx

@pytest.fixture
def override_get_db(app, db_session):
    """依赖注入覆盖:用测试DB替换真实DB"""
    app.dependency_overrides = lambda: db_session
    yield
    app.dependency_overrides.clear()测试金字塔

    /¯¯\
   / E2E \    ← 最少(关键路径,慢)
/_______\
/___________\
/ 集成测试   \← 适量(API+DB,中等速度)
/___________\
/____________\← 最多(纯函数,快)
/单元测试   \
/____________\MonkeyCode帮你天生金字塔底层80%的单位测试,集成测试和E2E只写关键路径。
测试驱动开发(TDD):让MonkeyCode先做TDD

用TDD方式实现[功能描述]:
1. 先写失败的测试(Red)
2. 写最少代码让测试通过(Green)
3. 重构(Refactor)
请按TDD循环给出完整代码常见测试场景速查

场景Mock工具示例HTTP哀求unittest.mock.patch + respxrespx.get(url).mock(return_value=...)数据库pytest-mock + 事件回滚mocker.patch("db.fetch_one")Redispytest-redis + fixturefake_redis.get(...)时间freezegun@freeze_time("2024-01-01")情况变量monkeypatchmonkeypatch.setenv("KEY", "val")文件IOtmp_path fixturewith open(tmp_path/"f.txt") as f:MonkeyCode Prompt模板

给以下Python函数写完整的pytest单元测试:
[粘贴函数代码]

要求:
1. 正常流程:覆盖所有分支
2. 异常流程:覆盖所有except分支
3. 边界条件:空输入、None、极大值
4. 参数化测试:多组输入输出
5. Mock所有外部依赖(DB/Redis/HTTP)
6. 覆盖率目标:100%行覆盖
7. 包含conftest.py共享fixture踩坑实录

坑表现办理方案Mock对象范例不对assert_called_with失败用isinstance查抄Mock参数范例异步测试报错RuntimeWarning: coroutine not awaited用@pytest.mark.asyncio装饰器测试间数据污染测试A的修改影响了测试B每个测试用独立fixture,事件回滚Mock不见效patch路径不对patch的是利用处不是界说处覆盖率造假__init__.py拉低覆盖率--cov-report=term-missing找出未覆盖行总结

单位测试的焦点代价是让你敢重构。MonkeyCode能帮你:

[*]从现有代码反向天生完备单位测试
[*]按TDD流程先写测试再写代码
[*]Mock全部外部依靠,测试纯快不依靠外部
[*]天生conftest.py共享fixture,淘汰重复代码
记取:测试覆盖率不是目标,快速定位Bug才是。优先测试焦点业务逻辑和边界条件。

免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.
页: [1]
查看完整版本: MonkeyCode写单位测试:从零到高覆盖率