Why TDD in Rust is Not Boring, But Beneficial?
\n\nHave you ever written code that seemed to work "correctly," only to catch a bug in the most unexpected place? Sounds familiar. Test-Driven Development (TDD) is a method that turns the usual process upside down. Instead of writing code first and then wondering if it works, you first write a test that should fail, and then you write just enough code to make the test pass.
\n\nRust is a language that already protects you from a whole class of errors at the compiler level (goodbye segfaults and data races!). But even the compiler doesn't guarantee your logic is correct. TDD in Rust adds another layer to this safety: you don't just write code, you document the expected behavior directly in the tests. And that's incredibly cool.
\n\nIn this article, we'll break down TDD in Rust with live examples. You'll see how to write tests before implementation, how to run them with cargo test, and how to refactor code without fear of breaking anything. Let's go!
What is TDD and Why is Rust Ideal for It?
\n\nTDD stands for Test-Driven Development. The cycle is simple and elegant:
\n\n- \n
- Red — Write a test that describes new functionality. Run it — the test fails (logically, the code doesn't exist yet). \n
- Green — Write the minimal code to make the test pass. Don't think about beauty, only about passing the test. \n
- Refactor — Improve the code without changing its behavior. The tests remain green. \n
Rust is ideal for TDD for several reasons:
\n- \n
- Built-in testing system —
cargo testworks out of the box, no need to install third-party frameworks. \n - Strong typing — The compiler catches type errors, and tests catch logic errors. Together, they are unbeatable. \n
- Ownership and borrowing — Tests help ensure you work with memory correctly without creating unnecessary copies. \n
- Community and documentation — The Rust community actively promotes testing; there are plenty of examples. \n
Setting Up a Project for TDD
\n\nLet's create a new project:
\ncargo new tdd_rust_example\ncd tdd_rust_example\n\nBy default, cargo new creates a src/main.rs file with a simple program. We'll be writing a library, so let's rename it to src/lib.rs (or simply delete main.rs and create lib.rs). Nothing needs to be changed in Cargo.toml — tests are already included.
By the way, if you are writing only a library (without an executable file), use cargo new --lib.
First Test: Red
\n\nImagine we want to write a function that adds two numbers. According to TDD, we first write a test. Open src/lib.rs and add:
#[cfg(test)]\nmod tests {\n #[test]\n fn test_add() {\n let result = add(2, 3);\n assert_eq!(result, 5);\n }\n}\n\nRun:
\ncargo test\n\nYou will see a compilation error — the add function is not defined. This is normal. We are in the red zone.
Green: Minimal Implementation
\n\nNow let's write just enough code to make the test pass. Add the add function above the test module:
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\nNotice use super::*; — this imports everything from the parent module (our code) into the test module.
Run cargo test again. Green! The test passes. We did the minimum — returned the sum. It seems