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]