可测试函数需明确输入输出、无隐式依赖、无副作用、返回具体值、避免修改可变输入,优先纯逻辑测试而非过度mock。
可测试的函数不能依赖全局状态或隐式环境,比如直接读配置文件、调用 time.time()、修改模块级变量。测试时你得能“喂”进去确定的输入,拿到确定的输出。
常见错误现象:test_get_user() 有时通过有时失败,因为函数内部调用了 datetime.now() 或读了本地 config.yaml;或者函数里写了 print() 或 logging.info(),导致断言逻辑被干扰。
now=None,配置用 config_dict=None
open()、requests.get()),改用传入已准备好的数据或 mock 对象None 表示成功,而用布尔值或枚举;别靠打印日志判断逻辑分支如果函数接收一个 list 或 dict 并直接修改它,测试时容易污染其他用例,也违背“输入确定 → 输出确定”的契约。
使用场景:比如写一个 add_tag(items, tag),本意是给每个 item 加个字段,但如果它直接 item["tag"] = tag,那测试完原数据就变了。
items = items.copy() 或 items = [dict(i) for i in items]
return [{"tag": tag, **item} for item in items]
inplace_add_tag(),且文档注明“修改输入列表”类型提示不是装饰,它是测试友好性

mypy 能帮你提前发现传错类型的问题,更重要的是——测试用例写起来更有依据。
参数差异直接影响测试覆盖粒度。比如一个函数声明为 def parse_date(s: str, default: Optional[datetime] = None),你就知道至少要测:s 是空字符串、非法格式、合法 ISO 字符串;default 是 None 和非 None 两种路径。
Union 或 Optional 显式表达可能的类型分支,避免运行时才抛 AttributeError
TypedDict 或 dataclass 封装,比裸 dict 更易构造测试输入有人为了“让测试快”,把函数里所有依赖都 mock 掉,结果实际函数体一行没跑,只验证了“是否调用了 mock”。这不是测试函数,是在测试调用顺序。
性能影响:过度 mock 可能让测试失去真实意义;兼容性影响:当底层接口变更(比如第三方库升级),mock 行为和真实行为脱节,测试照过但线上炸了。
now=datetime(2025,1,1) 比 mock datetime 模块更安全requests.post(),前者专注单元测试,后者走集成测试assert,而是让函数足够“干净”到你能放心地断言。越早把副作用、隐式依赖、模糊边界从函数里剔出去,后面补测试的成本就越低。