普贤教程 › 生态包
测试与对比
四个包解决「怎么证明代码是对的」:testkit 断言、tdtest 表驱动、quickcheck 属性测试、diff 差异。它们都是纯函数、零依赖,px test 之外也能直接当库用。
testkit:断言返回 Result,不打印不计数
这是最容易用错的地方:tk_equal 之类不做任何输出,成功返 Ok(null)、失败返 Err(msg)。累加与打印是调用方的事。
px
import testkit var total = 0 var failed = 0 var r1 = tk_equal(1 + 1, 2, "加法") total += 1 if r1.is_err(): failed += 1 print(r1) var r2 = tk_equal(1 + 1, 3, "加法") total += 1 if r2.is_err(): failed += 1 print(r2) tk_summary("demo", total, failed)
text
Err(期望 3,实际 2(加法)) FAIL: demo(1/2 失败)
tk_summary(name, total, failed) 是三个参数它不自己统计,全部靠你传。传
failed=0 就打印 PASS: name(N 断言),否则
FAIL: name(M/N 失败),并返回 bool——可以直接 if not tk_summary(...): exit(1)。可用断言:tk_true tk_false tk_equal tk_not_equal tk_contains tk_not_contains tk_approx tk_is_ok tk_is_err tk_type tk_len tk_nil tk_not_nil tk_match tk_greater tk_less tk_fail,另加 tk_tempdir(prefix) 建临时目录。
tdtest:表驱动
每条用例是一个 dict,必须带 fn(单参函数)与 args,断言二选一:want(相等)或 check(谓词)。
px
import tdtest def twice(x): return x * 2 def threetimes(x): return x * 3 var cases = [ {"name": "2*2", "fn": twice, "args": 2, "want": 4}, {"name": "3*2", "fn": threetimes, "args": 2, "want": 100} ] var r = td_run(cases).ok() print(r["pass"], r["fail"], r["total"]) print(str(r["failures"]))
text
1 1 2 [{got: 6, name: 3*2, want: 100}]
td_run 只有一个形参写成
td_run(cases, check_fn) 会得到 R1005: 调用 td_run 参数过多(2 个实参,1 个形参)。
断言函数要放进每条用例的 check 里。另外 fn 只接受单参——多参场景要自己包一层。quickcheck:属性测试,但反例不可捕获
成功时返回跑了多少次(不是 true):
px
import quickcheck def in_range(n): return n >= 0 and n <= 100 print(qc_for_int(0, 100, in_range, 50)) # 50
text
50反例直接终结进程
qc_for_int 内部对 qc_forall 的结果调 .unwrap(),而 PuXian 没有 try/catch:px
def bad(n): return n < 10 print("start") print(qc_for_int(0, 100, bad, 20))
text
start 运行时错误: 错误 [R1004] 67:53: unwrap 失败: Err(quickcheck: 反例(第 1 次): 52)
所以要「跑属性测试但不让进程死」只能用底层的 qc_forall(gen, prop, n),它返 Result:Err 里带反例值与第几次。qc_int(lo,hi) / qc_str(maxlen) / qc_list(gen, maxlen) / qc_bool() 是生成器。
diff:行级差异
px
import diff var a = "a\nb\nc" var b = "a\nB\nc" print(str(df_lines(a, b))) # 前缀表示 同/删/增 print(df_unified(a, b)) print(df_ratio(a, b))
text
[ a, -b, +B, c] --- a +++ b @@ -1,4 @@ a -b +B c 0.6666666666666666
hunk 头的行数比实际多 1
a 只有 3 行,头写的却是 @@ -1,4 @@。单行输入 x 也会写成 @@ -1,2 @@。
patch 命令对「行数偏大但内容完整」的 hunk 通常能容忍,但严格校验的工具有可能拒收。要用标准 unified diff,把头部行数自己减 1 重写。API:df_lines(带前缀的行列表)、df_opcodes(equal/replace/insert/delete 段)、df_unified(补丁文本,context 默认 3)、df_ratio(相似度,按行/词判等,不是字符级)、df_inline(行内差异)。