欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > 用 TDD 构建 Rust 命令行搜索功能:以 minigrep 为例

用 TDD 构建 Rust 命令行搜索功能:以 minigrep 为例

2025/2/14 1:37:52 来源:https://blog.csdn.net/weixin_43114209/article/details/145563693  浏览:    关键词:用 TDD 构建 Rust 命令行搜索功能:以 minigrep 为例

1. 测试驱动开发(TDD)简介

TDD 通常包含以下步骤:

  1. 编写一个会失败的测试,并确保它因我们期望的原因而失败。
  2. 仅编写足够的代码 让这个新测试通过。
  3. 重构 刚才写的代码,并保证所有测试仍然通过。
  4. 重复 步骤 1~3,不断迭代。

这种流程可以帮助我们保持较高的测试覆盖率,同时让需求或 API 在实现之前就被“测试驱动”明确下来。

2. 添加一个失败的测试

src/lib.rs 中,我们先移除调试用的 println!,并添加一个新的测试模块 tests。我们打算写一个函数 search(query, contents),返回所有包含 query 的行。以下是先行编写的测试(暂时不会编译通过):

#[cfg(test)]
mod tests {use super::*;#[test]fn one_result() {let query = "duct";let contents = "\
Rust:
safe, fast, productive.
Pick three.";assert_eq!(vec!["safe, fast, productive."],search(query, contents));}
}

测试说明

  • 我们的 query"duct"
  • contents 包含 3 行字符串,其中只有 "safe, fast, productive." 包含 “duct”。
  • 测试断言期望返回一个字符串切片向量,其中只有那一行。

如果此时我们尝试 cargo test,会发现编译都过不去:search 函数根本没有定义。我们要先写一个最简单的函数签名以让它能编译并执行测试(即让测试真正“失败”)。

3. 让测试编译,但先故意失败

src/lib.rs 中新增一个 search 函数的占位实现:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {Vec::new() // 暂时返回空
}
  • 这里指定了显式生命周期 'a,用于表明返回的切片依赖于 contents 的生命周期(而非 query)。
  • 目前我们只返回一个空向量,让测试会必然失败。

现在再 cargo test 会发现测试失败,且原因是结果为空,不匹配我们期望的那行内容。很好,这正是我们想在 TDD 第一步看到的现象。

4. 编写通过测试的最小实现

既然测试失败,接下来就在 search 函数里实现搜索逻辑,让它只返回包含 query 的行。需要的步骤包括:

  1. 按行迭代 contents
  2. 判断该行是否包含 query
  3. 如果包含,推入一个结果向量;
  4. 返回该向量。

完整示例:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {let mut results = Vec::new();for line in contents.lines() {if line.contains(query) {results.push(line);}}results
}
  • contents.lines() 会逐行迭代文本;
  • line.contains(query) 判断该行是否包含搜索词;
  • 若包含则 push 进结果向量。
  • 最终返回所有匹配行的集合。

再次测试

运行 cargo test,如果一切顺利,one_result 测试应当通过,说明最小逻辑已经满足需求。

5. 使用 search 函数

搜索逻辑完成后,我们就能在 run 函数(lib.rs 里)里调用它。示例:

pub fn run(config: Config) -> Result<(), Box<dyn std::error::Error>> {let contents = fs::read_to_string(&config.file_path)?;// 调用 searchlet results = search(&config.query, &contents);for line in results {println!("{}", line);}Ok(())
}

这样就能在 CLI 中输出每条匹配结果。

验证效果

假设命令:

$ cargo run -- body poem.txt

如果 poem.txt 中包含多行带有 “body” 的内容,终端会输出相应的行。搜不到时则不输出。

6. 思考与改进

当前 search 的实现虽然可用,但:

  1. 可用迭代器链简化:在后续我们介绍迭代器时,可以将 for 循环替换为更函数式的写法,比如使用 .filter.collect 等提高简洁性。
  2. 区分大小写或添加更多功能:比如做一个 search_case_insensitive
  3. 更多测试场景:可以补充多行、多匹配、不匹配等各式测试,进一步保证搜索逻辑稳健。

TDD 并非唯一可行的方法,但它在很多场景能够驱动你更加清晰地写出高覆盖率的测试,从而持续检验你的设计与需求是否一致。

7. 总结

在本篇中,我们展示了如何利用 测试驱动开发(TDD)minigrep 加入关键搜索功能:

  1. 先写一个失败测试,确定所需的函数签名与期望行为;
  2. 实现最小可行逻辑 让测试通过;
  3. 在实际代码中使用 并继续测试或改进。

TDD 在 Rust 中的实践尤为便利:

  • 将核心逻辑提取到 lib.rs 便于直接调用函数测试;
  • cargo test 快速运行与报告;
  • 随时用单元测试来检验我们的迭代改动。

到此,“minigrep” 工具已经能够读取文本文件、搜索指定关键词并打印结果。后续若要拓展(如环境变量设置、区分大小写等),也可借鉴相同思路,再补充更多测试用例,不断迭代。希望这对你在 Rust CLI 项目中实施 TDD 带来一些灵感与帮助!

祝你在 Rust 的 TDD 之路上收获满满!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com