Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
71: Testing in Rust
A few years ago, I was working on a custom caching layer for a high-traffic API. I spent an entire Tuesday afternoon chasing a bug that only appeared when the cache was exactly 50% full and a specific key was evicted. I kept trying to reproduce it by manually running the server and hitting endpoints with Curl, which is essentially like trying to find a needle in a haystack by burning the haystack down. It wasn't until I sat down and wrote a targeted test case that I realized I had a classic off-by-one error in my index calculation. That's the moment it clicked for me: if you're manually testing your logic, you're not testing it—you're just hoping it works.
Keeping Logic Close with Unit Tests
In Rust, the convention is to put your unit tests in the same file as the code they are testing. I know that feels messy if you're coming from Java or C#, but it's incredibly powerful because it allows your tests to access private functions and structs. You don't have to make everything pub just to verify it works.
To do this, we wrap our tests in a module—usually named tests—and mark it with the #[cfg(test)] attribute. This tells the Rust compiler, "Hey, only compile this module when I'm running cargo test." This keeps your final binary lean because the test code is completely stripped out of the production build.
pub fn add_to_inventory(count: u32, current: u32) -> u32 {
count + current
}
#[cfg(test)]
mod tests {
use super::*; // Import everything from the outer module
#[test]
fn test_inventory_addition() {
assert_eq!(add_to_inventory(5, 10), 15);
}
#[test]
fn test_inventory_zero() {
assert_eq!(add_to_inventory(0, 10), 10);
}
}
You'll notice I used assert_eq!. Rust provides a few of these macros; assert! checks if a boolean is true, while assert_eq! tells you exactly what the two values were if they didn't match, which saves you from having to add a bunch of print statements just to see what went wrong.
Treating Your Library as a User
Unit tests are great for the nitty-gritty details, but they have a blind spot: they know too much. Because they can see private internals, they don't tell you if your library is actually easy to use for someone else. That's where integration tests come in.
Integration tests live in a separate directory called tests/ at the root of your project. When you put a file in there, Cargo treats it as a separate crate. This means you can only call the public API of your library. If you find yourself struggling to set up an integration test because you can't access a necessary function, that's a huge red flag that your API design is too restrictive.
I usually use integration tests to verify "happy paths"—the primary workflows a user would actually take. If your library is a database wrapper, an integration test would be: "Can I open a connection, insert a row, and read it back?" without caring how the internal buffer management works.
Turning Documentation into Verified Code
One of my favorite features of Rust is documentation tests. We've all downloaded a library only to find that the examples in the README are outdated and don't actually compile. Rust solves this by allowing you to write tests directly inside your doc comments using triple slashes ///.
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
When you run cargo test, Rust actually parses those code blocks, compiles them, and runs them. If you change the function signature but forget to update the documentation, your tests will fail. It's a built-in guarantee that your documentation never lies to your users. I've found that writing doc tests first actually helps me design a cleaner API, as it forces me to think about the developer experience from the very first line of code.
📋 Practical Task
Hardening the WordCounter Logic
You are building a WordCounter utility that counts the number of words in a string. However, it needs to handle edge cases: it should ignore punctuation and treat words as case-insensitive.
Your Task:
- Implement a function
count_words(input: &str) -> usizethat returns the number of words. - Create a
mod testsblock using#[cfg(test)]. Write unit tests to verify that:- An empty string returns 0.
- A string with only spaces returns 0.
- A string with mixed punctuation (e.g., "Hello, world!") correctly identifies 2 words.
- Add a documentation test (using
///) that demonstrates a basic usage ofcount_wordsand asserts the result is correct. - Create a mock integration test scenario (conceptually) by ensuring your function is
puband can be called from an external context.
Hint: Consider using input.split_whitespace() and filtering out non-alphanumeric characters if you want to be thorough with punctuation.
There are no comments for now.