可测试函数需明确输入输出、无隐式依赖、无副作用、返回具体值、避免修改可变输入,优先纯逻辑测试而非过度mock。

函数必须有明确的输入和输出
可测试的函数不能依赖全局状态或隐式环境,比如直接读配置文件、调用 time.time()、修改模块级变量。测试时你得能“喂”进去确定的输入,拿到确定的输出。
常见错误现象:test_get_user() 有时通过有时失败,因为函数内部调用了 datetime.now() 或读了本地 config.yaml;或者函数里写了 print() 或 logging.info(),导致断言逻辑被干扰。
- 把所有外部依赖抽成参数:时间用
now=None,配置用config_dict=None - 避免在函数体内做 I/O(如
open()、requests.get()),改用传入已准备好的数据或 mock 对象 - 返回值要具体:别返回
None表示成功,而用布尔值或枚举;别靠打印日志判断逻辑分支
避免副作用,尤其是修改传入的可变对象
如果函数接收一个 list 或 dict 并直接修改它,测试时容易污染其他用例,也违背“输入确定 → 输出确定”的契约。
使用场景:比如写一个 add_tag(items, tag),本意是给每个 item 加个字段,但如果它直接 item["tag"] = tag,那测试完原数据就变了。
立即学习“Python免费学习笔记(深入)”;
传媒企业网站系统使用热腾CMS(RTCMS),根据网站板块定制的栏目,如果修改栏目,需要修改模板相应的标签。站点内容均可在后台网站基本设置中添加。全站可生成HTML,安装默认动态浏览。并可以独立设置SEO标题、关键字、描述信息。源码包中带有少量测试数据,安装时可选择演示安装或全新安装。如果全新安装,后台内容充实后,首页才能完全显示出来。(全新安装后可以删除演示数据用到的图片,目录在https://
- 默认对可变参数做浅拷贝:
items = items.copy()或items = [dict(i) for i in items] - 更稳妥的做法是不修改输入,而是返回新结构:
return [{"tag": tag, **item} for item in items] - 如果真需要原地修改,函数名必须明确体现副作用,比如叫
inplace_add_tag(),且文档注明“修改输入列表”
用类型提示 + 默认参数暴露行为边界
类型提示不是装饰,它是测试友好性的基础设施。IDE 和 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 可能让测试失去真实意义;兼容性影响:当底层接口变更(比如第三方库升级),mock 行为和真实行为脱节,测试照过但线上炸了。
- 优先测试函数本身:给定输入,检查返回值是否符合预期
- 只 mock 真正难控的部分(如网络、时间、随机数),且尽量用轻量方式,比如传
now=datetime(2020,1,1)比 mockdatetime模块更安全 - 如果函数必须调外部服务,把它拆成两层:纯逻辑函数 + 一个薄的胶水函数负责调
requests.post(),前者专注单元测试,后者走集成测试
assert,而是让函数足够“干净”到你能放心地断言。越早把副作用、隐式依赖、模糊边界从函数里剔出去,后面补测试的成本就越低。









