Cargo & Testing
Cargo: The Rust Build System and Package Manager
Section titled “Cargo: The Rust Build System and Package Manager”Cargo is Rust’s official build system and package manager. Every Rust project is a crate, and Cargo manages its dependencies, builds, tests, and documentation.
Essential Cargo Commands
Section titled “Essential Cargo Commands”# Create a new binary projectcargo new my_projectcd my_project
# Create a new library cratecargo new my_lib --lib
# Build in debug mode (fast compile, unoptimized)cargo build
# Build in release mode (slow compile, optimized)cargo build --release
# Build and run in one stepcargo run
# Run with argumentscargo run -- arg1 arg2
# Check for compile errors without producing a binarycargo check
# Add a dependencycargo add serde --features derive
# Update dependencies to latest compatible versionscargo update
# Format codecargo fmt
# Run the lintercargo clippyProject Structure
Section titled “Project Structure”flowchart TD root["my_project/"] toml["Cargo.toml — manifest: name, version, dependencies"] lock["Cargo.lock — exact dependency versions (commit for binaries)"] src["src/"] main["main.rs — entry point for binary crates"] lib["lib.rs — entry point for library crates"] tests["tests/"] itest["integration_test.rs — integration tests (optional)"] root --> toml root --> lock root --> src root --> tests src --> main src --> lib tests --> itest
Cargo.toml Anatomy
Section titled “Cargo.toml Anatomy”[package]name = "my_project"version = "0.1.0"edition = "2021"
[dependencies]serde = { version = "1", features = ["derive"] }tokio = { version = "1", features = ["full"] }anyhow = "1"
[dev-dependencies]# only compiled for tests and benchmarkspretty_assertions = "1"Unit Testing with #[test]
Section titled “Unit Testing with #[test]”Rust has testing built into the language and Cargo — no extra test framework needed. Annotate a function with #[test] to mark it as a test.
fn add(a: i32, b: i32) -> i32 { a + b}
#[test]fn test_add() { assert_eq!(add(2, 3), 5); assert_eq!(add(-1, 1), 0); assert_ne!(add(2, 3), 6);}Run all tests with:
cargo testThe #[cfg(test)] Module Pattern
Section titled “The #[cfg(test)] Module Pattern”The idiomatic place for unit tests is inside the source file they test, inside a mod tests block gated by #[cfg(test)]. Cargo only compiles this module when running cargo test, keeping it out of production binaries.
pub fn multiply(a: i32, b: i32) -> i32 { a * b}
pub fn divide(a: f64, b: f64) -> Option<f64> { if b == 0.0 { None } else { Some(a / b) }}
#[cfg(test)]mod tests { use super::*; // bring parent module items into scope
#[test] fn test_multiply() { assert_eq!(multiply(3, 4), 12); assert_eq!(multiply(0, 100), 0); assert_eq!(multiply(-2, 5), -10); }
#[test] fn test_divide_by_zero() { assert_eq!(divide(10.0, 0.0), None); }
#[test] fn test_divide_normal() { let result = divide(10.0, 4.0).unwrap(); assert!((result - 2.5).abs() < f64::EPSILON); }}Test Assertions
Section titled “Test Assertions”#[cfg(test)]mod tests { #[test] fn demonstrate_assertions() { // equality assert_eq!(2 + 2, 4); assert_ne!(2 + 2, 5);
// boolean assert!(4 > 3); assert!(!"hello".is_empty());
// with custom message assert_eq!(1 + 1, 2, "basic arithmetic must hold");
// panics // use #[should_panic] to assert a function panics }
#[test] #[should_panic(expected = "divide by zero")] fn test_panic() { let _ = 1 / 0; // this panics — test passes because we expect it }}Integration Tests
Section titled “Integration Tests”Files under tests/ are integration tests. They test your crate’s public API as an external consumer would.
use my_lib::multiply;
#[test]fn integration_multiply() { assert_eq!(multiply(6, 7), 42);}Run only integration tests:
cargo test --test integration_testRun a specific test by name:
cargo test test_divideRun tests with output visible:
cargo test -- --nocaptureTesting Async Code with tokio
Section titled “Testing Async Code with tokio”For async functions, use #[tokio::test]:
#[cfg(test)]mod tests { use super::*;
#[tokio::test] async fn test_async_fetch() { let result = fetch_data(1).await; assert!(!result.is_empty()); }}Add tokio to [dev-dependencies] to use #[tokio::test] without pulling it into your production binary if you do not need it there.