AI 辅助 Rust 单元测试:让模型先生成边界用例,再写测试函数
AI 辅助 Rust 单元测试让模型先生成边界用例再写测试函数一、写测试最怕的不是写不出来而是根本想不到刚开始给 Rust 函数写测试时我的做法很朴素——把函数签名扫一眼脑子里蹦出三五个正常输入assert_eq!一跑就提交了。后来被编译器毒打多了才意识到让测试有价值的从来不是正常情况而是那些你没考虑到的边界。空字符串传入会怎样Option::None有没有覆盖溢出边界在哪个值触发这些问题的答案靠直觉根本想不全。作为自学编程的人没有系统学过测试方法论每次写边界用例都像是在碰运气——运气好想到七八个运气差漏掉一半。如果有一个边界用例检查清单该多好——不是从脑子里搜刮而是让 AI 模型根据函数签名和语义系统性地生成一份候选清单。人来做筛选和最终决策机器来做穷举。二、核心方法先筛边界再写代码的六步工作流这套方法的核心思想很简单把想边界和写测试拆成两个独立步骤。先让 AI 帮你列出所有可能的边界维度人工筛选后再让 AI 生成测试函数骨架。flowchart TD A[ 输入函数签名与语义说明] -- B[ AI 分析函数行为与参数空间] B -- C[ 生成边界用例候选列表br/按维度分类空值/极值/类型边界/组合爆炸] C -- D{ 人工筛选br/是否合理是否遗漏} D -- 补充 -- E[✏️ 手动追加边界用例] D -- 确认 -- F[ AI 生成测试函数代码br/每个边界对应一个 #[test]] E -- F F -- G[✅ 编译并运行 cargo test] G -- 失败 -- H[ 分析失败原因br/是测试写错了还是函数有 bug] H -- F G -- 全部通过 -- I[ 提交代码]流程中有三个关键环节值得展开第一给 AI 的上下文要足够具体。不能只丢一个函数签名过去要附上函数的业务语义和预期行为。比如这个函数解析一行配置格式为key value支持双引号包裹的值和#注释比光给一个fn parse(line: str) - Option(String, String)效果好得多。第二人工筛选是质量闸门。AI 生成的边界列表一定会有过度防御的情况——比如对一个只处理 ASCII 的函数它可能生成 Unicode 组合字符的测试用例。这些在实际场景下是噪音需要果断删掉。第三生成的测试代码必须编译通过才算数。有些 AI 会编造不存在的 API 或用错 trait 约束。所以生成 → 编译 → 修正这个循环必不可少。三、实战演示用 parse_config_line 走一遍完整流程下面用一个 Rust 配置解析函数来完整走一遍这套流程。3.1 待测函数/// 解析单行配置格式为 key value 或 key value支持 # 注释。 /// - 空行或纯注释行返回 None /// - key 和 value 会被 trim /// - 双引号包裹的 value 会去掉引号 /// - 返回 (key, value) 的元组 fn parse_config_line(line: str) - Option(String, String) { // 去除 # 注释 let content line.split(#).next().unwrap_or().trim(); if content.is_empty() { return None; } // 按第一个 分割 let mut parts content.splitn(2, ); let key parts.next()?.trim().to_string(); let value_raw parts.next()?.trim(); if key.is_empty() { return None; } // 去掉引号 let value value_raw .strip_prefix() .and_then(|s| s.strip_suffix()) .unwrap_or(value_raw) .to_string(); Some((key, value)) }3.2 Prompt 模板让 AI 生成边界用例以下是我实际使用的 Prompt你是一个 Rust 测试专家。请分析以下函数按维度生成边界用例清单。 【函数】 rust /// 解析单行配置格式为 key value 或 key value支持 # 注释。 /// - 空行或纯注释行返回 None /// - key 和 value 会被 trim /// - 双引号包裹的 value 会去掉引号 /// - 返回 (key, value) 的元组 fn parse_config_line(line: str) - Option(String, String) { // 去除 # 注释 let content line.split(#).next().unwrap_or().trim(); if content.is_empty() { return None; } // 按第一个 分割 let mut parts content.splitn(2, ); let key parts.next()?.trim().to_string(); let value_raw parts.next()?.trim(); if key.is_empty() { return None; } // 去掉引号 let value value_raw .strip_prefix() .and_then(|s| s.strip_suffix()) .unwrap_or(value_raw) .to_string(); Some((key, value)) } 请按以下维度输出边界用例每类给出具体输入和预期输出 1. 空值与缺失空字符串、纯空白、缺 、缺 value 2. 边界字符key/value 为单个字符、 前后有空格 3. 注释相关行首 #、行中 #、value 内含 # 4. 引号处理带双引号、只有左引号、引号内部为空 5. 特殊输入key 含中文、value 含 、整行只有 输出格式 - 输入: ... - 预期输出: Some((key, value)) 或 None - 边界维度: 简短说明3.3 AI 输出的边界用例清单AI 按五个维度输出了以下 16 个边界用例节选核心条目#输入预期输出边界维度1None空字符串2 None纯空白3# 注释None纯注释行4keyNone缺少等号5 valueNone缺少 key6keySome((key, ))缺少 value7abSome((a, b))单字符 key/value8 key value Some((key, value))前后空白9key\val\ue\Some((key, val\ue))value 内含引号10keyval#注释Some((key, val))行中注释11key\\Some((key, ))引号内为空12key\only_leftSome((key, \only_left))只有左引号13kv1v2Some((k, v1v2))value 含等号14配置项值Some((配置项, 值))key/value 中文15None整行只有等号16key Some((key, ))value 为空白3.4 人工筛选删掉什么、补上什么拿到这份清单后我做了两件事删掉了第 9 条value 内含引号和第 12 条只有左引号因为当前函数注释明确写了格式为key valuevalue 内部不支持转义引号这两个 case 属于当前版本不覆盖的边界。补充了一条连续多行解析时的状态污染——不过这是集成测试的范畴单元测试阶段先跳过。最终保留 14 个用例。3.5 Prompt 模板让 AI 生成测试函数筛选完成后再用以下 Prompt 生成测试代码请为以下 Rust 函数生成 #[test] 测试函数覆盖我筛选后的边界用例清单。 【边界用例清单】粘贴上面筛选后的表格 要求 1. 每个测试函数用 #[test] 标注 2. 函数名使用 test_parse_config_line_场景英文描述 格式 3. 使用 assert_eq! 比较实际输出和预期 4. 所有测试放在 #[cfg(test)] mod tests 中 5. 代码可以直接编译通过AI 生成的测试代码节选#[cfg(test)] mod tests { use super::*; #[test] fn test_empty_string() { assert_eq!(parse_config_line(), None); } #[test] fn test_whitespace_only() { assert_eq!(parse_config_line( ), None); } #[test] fn test_comment_line() { assert_eq!(parse_config_line(# 这是注释), None); } #[test] fn test_missing_equals() { assert_eq!(parse_config_line(key), None); } #[test] fn test_missing_key() { // 左边没有 key应当返回 None assert_eq!(parse_config_line( value), None); } #[test] fn test_missing_value() { // key 后面有 但没有 valuevalue 应当是空字符串 assert_eq!( parse_config_line(key), Some((key.to_string(), .to_string())) ); } #[test] fn test_trim_whitespace() { assert_eq!( parse_config_line( key value ), Some((key.to_string(), value.to_string())) ); } #[test] fn test_inline_comment_removed() { // # 及其后面的内容应该被忽略 assert_eq!( parse_config_line(keyval#这是注释), Some((key.to_string(), val.to_string())) ); } #[test] fn test_chinese_chars() { assert_eq!( parse_config_line(配置项值), Some((配置项.to_string(), 值.to_string())) ); } }运行cargo test14 个测试全部通过0 个失败。这说明两件事一是函数的实现基本正确二是 AI 对 Rust 标准库 API 的掌握足以生成可直接编译的测试代码。四、AI 生成的边界列表你该信多少坦率地说AI 在边界分析上也并非全能。以下是实际使用中发现的几类典型遗漏和评估方法。4.1 AI 容易遗漏的三类边界平台相关边界。比如usize::MAX在 64 位和 32 位平台上值不同但 AI 通常只按 64 位给出用例。如果你的库需要支持 WASM32 位这个差异可能变成 bug。并发状态边界。对于涉及共享状态的函数如mut self方法AI 很少主动生成两个线程同时调用的边界场景。这是因为它拿到的是单个函数的签名看不到调用上下文。组合爆炸边界。当函数有 3 个以上参数时AI 倾向于独立分析每个参数很少生成参数 A 取极值 参数 B 取极值的交叉用例。比如一个分页函数fn list(page: u32, size: u32)AI 可能分别测试page0和size0但不一定会测试page0, size0的组合。4.2 自检清单人工验证的三个维度在信任 AI 输出的边界列表之前建议用以下三个问题过一遍验证维度检查方法类型覆盖每个参数的类型取值范围是否穷尽Option 的 None/Some、Result 的 Ok/Err、数值的 0/负/MAX业务语义用例是否覆盖了注释里描述的所有行为空行返回 None、去掉双引号跨参数组合是否存在两个以上参数交互的边界即使当前没有也要记一笔留待集成测试这套自检不是要推翻 AI 的输出而是补上它看不到全局的短板。实际工作中我把这个三问清单贴在终端旁边每次筛选边界用例时扫一遍平均能多抓到 1~2 个 AI 遗漏的 case。五、总结这套AI 先生成边界、再写测试的流程本质是把测试设计中最消耗心力的穷举工作交给模型把人留在更擅长的判断和决策位置上。回归到工程实践有三点值得带走Prompt 的质量决定边界清单的质量。给 AI 的上下文要包含函数签名、业务语义和预期行为三要素敷衍的 Prompt 只会得到敷衍的边界。人工筛选是必要的安全网。平台差异、并发场景、参数组合这三类边界AI 目前的覆盖率都不够必须靠人补。编译通过 ≠ 思路正确。AI 生成的测试代码语法没问题但断言的预期值可能编错——比如把None写成Some((, ))。跑测试前先扫一眼每个 assert 的预期值是否合理。测试是代码的第一道防线而一份扎实的边界用例清单就是这道防线上最密的那张网。