核心是用 inspect.getmembers 获取可调用对象,再按命名、参数、装饰器等规则筛选测试候选函数;需绕过 pytest 收集逻辑直接解析源码,安全调用前须检查副作用并加异常捕获。

怎么用 inspect 扫描模块里的测试候选函数
核心是靠 inspect.getmembers 拿出所有可调用对象,再过滤出你定义的“可测函数”——比如名字带 test_、参数不含 self 或 cls、没被 @staticmethod 以外的装饰器干扰的函数。
实操建议:
- 用
inspect.isfunction(obj)排除方法、lambda、内置函数 - 用
inspect.signature(obj)检查参数数量和默认值,避免扫到需要手动传参的函数(比如def calc(x, y):就没法直接跑) - 跳过以
_开头的函数,也跳过__双下划线方法,它们通常不是测试目标 - 注意:如果函数在类里且没加
@staticmethod,inspect.isfunction会返回False,它会被识别成instancemethod,得单独处理
为什么 pytest 的 collect_only 不适合做自动生成依据
因为 pytest --collect-only 输出的是已注册为测试项的函数,依赖 conftest.py、pytest_plugins、甚至 __pytest_configure 钩子——它反映的是“pytest 怎么理解测试”,不是“代码里有哪些函数能被测”。你想自动补测试用例,得绕过 pytest 的收集逻辑,直接读源码结构。
常见错误现象:
立即学习“Python免费学习笔记(深入)”;
- 用
pytest.main(['--collect-only'])在脚本里调用,结果卡住或返回空——因为 pytest 启动时会尝试导入所有测试文件,可能触发未初始化的全局变量或连接逻辑 - 扫描结果和
pytest --collect-only不一致,误以为反射漏了函数,其实是 pytest 默认只认test_*.py里test_*开头的函数,而你的反射可能扫到了utils.py里的test_helper - 忽略
if __name__ == '__main__':块里的函数定义,它们在模块被 import 时不执行,inspect根本看不到
生成测试用例时怎么安全调用原函数并捕获异常
不能直接 func(),尤其当函数有副作用(写文件、发请求、改数据库)或依赖外部状态时。得先判断是否“可安全执行”——最简单的方式是检查函数体是否含危险关键词,或加白名单装饰器标记。
实操建议:
- 用
inspect.getsource(func)提取函数体字符串,粗筛'open('、'requests.'、'db.'等关键词(不完美但够用) - 更可靠的做法:要求函数必须带
@testable装饰器,生成器只处理被显式标记的函数 - 调用时统一包在
try/except Exception里,记录repr(e)而不是str(e),避免中文异常信息乱码 - 别用
eval或exec动态构造调用表达式——容易执行任意代码,尤其当函数名来自用户输入或文件名时
getattr 和 importlib.import_module 组合加载函数为什么容易失败
常见错误不是语法错,而是路径错、缓存错、相对导入错。比如模块路径写成 myproject.tests.test_math,但实际入口是 python -m pytest tests/,此时 myproject 根目录不在 sys.path 里,importlib.import_module 就抛 ModuleNotFoundError。
关键细节:
- 优先用
importlib.util.spec_from_file_location+spec.loader.exec_module加载绝对路径的 .py 文件,绕过 sys.path 依赖 - 加载后别直接
getattr(module, func_name),先确认func_name in dir(module),否则遇到同名变量会静默失败 - 同一个模块被多次
import_module,Python 会返回缓存的 module 对象,但如果你在中间修改了源码又没 reload,反射拿到的还是旧函数对象 - Windows 下路径分隔符混用(
\vs/)可能导致spec_from_file_location返回 None,务必用pathlib.Path.resolve()归一化
真正麻烦的从来不是反射本身,而是函数边界不清:没类型注解、参数靠文档、返回值隐式、副作用藏在深处。自动生成只能覆盖“能猜到怎么调”的那部分,剩下的还得人看。


















