很多人写单测半途而废,不是因为不会写断言,而是因为测试一跑就要连数据库、发 HTTP、等定时任务,慢且不稳定。关键是把外部依赖挡在边界之外。
一、先测纯逻辑,别急着测全流程
把一个方法里「算」的部分和「取数」的部分拆开:
# 不好:方法里直接查库、直接算、直接发消息
def create_order(user_id, items): ...
# 好:计算逻辑独立出来,可单独测
def calc_amount(items, coupon):
...
def create_order(user_id, items):
amount = calc_amount(items, load_coupon(user_id))
save_order(...)
calc_amount 这种纯函数是单测性价比最高的目标:不用 mock、跑得飞快、边界条件一目了然。
二、依赖用接口隔离
数据库、缓存、第三方客户端都通过接口注入,测试时替换成内存实现或 mock:
class Repo(Protocol):
def get(self, key: str) -> dict | None: ...
def service(repo: Repo, key: str):
obj = repo.get(key)
...
这样测试只验证「拿到 X 时该做 Y」,不需要真的启一个数据库。
三、测试该断言什么
优先断言行为结果(返回值、状态变化、是否调用了某个写操作),而不是断言内部实现细节(调用了哪个私有方法、调用几次)。后者一重构测试就全红,维护成本极高。
一个用例覆盖一个场景,命名说清条件与预期:
test_余额不足时_应拒绝下单并保留库存
test_重复请求_应返回同一订单号
四、边界清单
写用例时按这张表过一遍,覆盖度通常就够了:
- 空值 / 空集合 / None
- 边界值:0、负数、最大值、刚好等于阈值
- 重复与并发:同一请求两次、同一资源竞争
- 外部失败:超时、返回错误、返回空
- 状态机:非法状态跳转是否被拒
五、哪些不该进单测
跨服务流程、真实数据库约束、消息队列投递,这些交给集成测试,用容器起一套真实依赖,数量少但跑得稳。单测追求的是毫秒级反馈,一旦某个用例要等 2 秒,开发者就不会愿意频繁跑了。
小结
把计算逻辑抽成纯函数、把 I/O 挡在接口后面、断言行为而非实现。做到这三点,单测就从负担变成了改代码时的底气。
- THE END -
最后修改:2025年8月14日
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://mi-blog.cn/archives/714
共有 0 条评论