为什么Rust中的TDD既不枯燥又有利可图?
\n\n您是否曾经写过“看似正确”的代码,却在最意想不到的地方遇到bug?很熟悉吧。测试驱动开发(TDD)是一种将常规流程彻底颠覆的方法。与其先写代码再猜测它是否工作,不如先编写一个应该失败的测试,然后编写刚好能让测试通过的代码。
\n\nRust是一种在编译器层面就能保护您免受一整类错误(告别段错误和数据竞争!)的语言。但即使编译器也不能保证您的逻辑正确。Rust中的TDD为这种安全性又增加了一层:您不仅仅是编写代码,而是在测试中直接记录预期行为。这非常酷。
\n\n在本文中,我们将通过实际示例剖析Rust中的TDD。您将看到如何在实现之前编写测试,如何使用cargo test运行它们,以及如何在不担心破坏任何东西的情况下重构代码。开始吧!
什么是TDD,为什么Rust是它的理想选择?
\n\nTDD代表测试驱动开发。这个循环简单而优雅:
\n\n- \n
- 红色 — 编写描述新功能的测试。运行——测试失败(很合理,代码还不存在)。 \n
- 绿色 — 编写最少的代码使测试通过。不考虑美观,只考虑通过测试。 \n
- 重构 — 改进代码而不改变其行为。测试保持绿色。 \n
Rust非常适合TDD,原因如下:
\n- \n
- 内置测试系统 —
cargo test开箱即用,无需安装第三方框架。 \n - 强类型 — 编译器捕获类型错误,测试捕获逻辑错误。两者结合无懈可击。 \n
- 所有权和借用 — 测试有助于确保您正确处理内存,不创建不必要的副本。 \n
- 社区和文档 — Rust社区积极推广测试,示例丰富。 \n
为TDD配置项目
\n\n创建一个新项目:
\ncargo new tdd_rust_example\ncd tdd_rust_example\n\n默认情况下,cargo new会创建一个包含简单程序的src/main.rs文件。我们将编写一个库,因此将其重命名为src/lib.rs(或者直接删除main.rs并创建lib.rs)。Cargo.toml中无需更改任何内容——测试已经包含在内。
顺便说一句,如果您只编写库(没有可执行文件),请使用cargo new --lib。
第一个测试:红色
\n\n假设我们要编写一个将两个数相加的函数。按照TDD,我们先编写测试。打开src/lib.rs并添加:
#[cfg(test)]\nmod tests {\n #[test]\n fn test_add() {\n let result = add(2, 3);\n assert_eq!(result, 5);\n }\n}\n\n运行:
\ncargo test\n\n您将看到编译错误——函数add未定义。这很正常。我们处于红色区域。
绿色:最小实现
\n\n现在编写刚好能让测试通过的代码。在测试模块上方添加add函数:
fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n \n #[test]\n fn test_add() {\n let result = add(2, 3);\n assert_eq!(result, 5);\n }\n}\n\n注意use super::*;——这将从父模块(我们的代码)导入所有内容到测试模块中。
再次运行cargo test。绿色!测试通过了。我们做了最少的工作——返回了和。看起来