You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 15:09:18