Erlang Quviq QuickCheck符号调用测试缓存触发badfun错误
问题根因
抛出badfun错误的核心原因是对Quviq QuickCheck的符号调用(symbolic call)机制理解有偏差,加上Erlang严格求值顺序的影响:
- 你生成的
TT1变量本质是符号调用的元组描述结构:{call, test_cache, mkt, [set, {V,C}]},这只是一个普通Erlang元组,用来记录“后续需要调用test_cache:mkt/2函数”的信息,本身不是可直接调用的函数。 - 你写的
TT1(new)是尝试把元组作为函数传入new参数调用,Erlang原生不支持元组作为可调用实体,会直接抛出badfun异常。 - 外层包裹
eval(TT1(new))完全无法规避错误:Erlang采用严格求值策略,执行eval/1前会先计算括号内的参数值,TT1(new)这一步已经触发崩溃,代码根本不会进入eval的执行逻辑。 - 你在
mkt/2里加的io:format打印只有在mkt函数实际被执行时才会触发,符号调用生成阶段mkt根本没有运行,自然也不会返回你定义的匿名函数。
修复方案
两种实现方式都可以正常运行,按需选择即可:
方案1:分两步求值(逻辑更直观)
先单独对符号调用做eval,拿到mkt/2返回的实际匿名函数后,再传参调用:
prop_cache()-> ?FORALL({K1, V1, C1, TT1}, term_transf(key()), begin {ok, F} = cache:new(999999), % 先执行符号调用,拿到mkt返回的匿名函数 MktFun = eval(TT1), % 再调用匿名函数获取结果做断言 equals({changed_value, V1, C1}, MktFun(new)) end ).
方案2:嵌套符号调用(适配纯符号测试场景)
如果需要全链路用符号表示(比如后续要做收缩调优),可以新增一个辅助函数封装匿名函数调用逻辑,把整个调用过程做成可被eval递归处理的嵌套符号结构:
% 注意:这个辅助函数必须导出,eval才能正常调用 call_mkt_ret_fun(MktFun, Arg) -> MktFun(Arg). % 调整测试数据生成逻辑,把整个调用链路包成符号call term_transf(KeyGen) -> oneof( [ ?LET({K, V, C}, {KeyGen, int(), cost()}, return ({K, V, C, {call, ?MODULE, call_mkt_ret_fun, [sym_mkt(set,{V,C}), new]}})) ]). prop_cache()-> ?FORALL({K1, V1, C1, TT1}, term_transf(key()), begin {ok, F} = cache:new(999999), % eval会递归求值所有嵌套的call结构,自动完成从mkt调用到匿名函数调用的全流程 equals({changed_value, V1, C1}, eval(TT1)) end ).
注意事项
- QuickCheck的符号调用本质是用Erlang数据结构表示的待执行AST,只有传给
eval/1时才会实际执行,求值前所有{call, ...}结构都只是普通元组,不能当做函数、进程ID这类运行期实体直接使用。 - 注意区分测试数据生成阶段和测试用例执行阶段:生成阶段拿到的符号结构都只是执行逻辑的描述,只有经过
eval/1处理后,才是实际运行的Erlang值。
内容的提问来源于stack exchange,提问作者Piskator
相关产品推荐
相关产品推荐

