| |

Rust 46 ๐Ÿฆ€ Writing Automated Unit Tests and Documentation Tests

Rust treats testing as a first-class part of the language. The cargo test command is built into the toolchain, the test functions are ordinary functions annotated with the #[test] attribute, and the test module is the ordinary module with the #[cfg(test)] attribute. There is no separate test framework to install, no configuration file to write, and no test runner to configure. The cargo test discovers the test functions, runs them in parallel, captures the output, and reports the results. The testing is the part of the language, and the cargo test is the command that runs it.

The unit tests live in the same file as the code they test. The convention is the #[cfg(test)] mod tests module at the bottom of the file, and the use super::* imports the parent module’s items. The #[cfg(test)] attribute tells the compiler to include the module only in the test build, so the test code does not ship in the release binary. The #[test] attribute marks the test function, and the function passes when it returns and fails when it panics. The assert!, the assert_eq!, and the assert_ne! macros are the assertions, and the #[should_panic] attribute marks the test that must panic.

The integration tests live in the tests directory, and each file is a separate crate that uses the library as the external consumer. The documentation tests live in the doc comments, and the cargo test runs the code examples in the /// comments. The three kinds of tests โ€” the unit, the integration, and the documentation โ€” form the testing strategy, and the cargo test runs all of them. The #[ignore] attribute skips the expensive tests, and the cargo test -- --ignored runs them.

This chapter covers three areas. First, why the Rust testing exists โ€” the built-in testing and the test-as-code. Second, the unit tests โ€” the #[cfg(test)], the #[test], the assertions, the #[should_panic], the Result return, and the private function testing. Third, the integration and the documentation tests โ€” the tests directory, the doc comments, the cargo test --doc, and the test organization. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the test organization.

Key point: The Rust testing is built into the language. The #[cfg(test)] module contains the unit tests, the #[test] attribute marks the test functions, and the cargo test runs them. The assert!, the assert_eq!, and the assert_ne! macros are the assertions. The #[should_panic] marks the tests that must panic, and the Result<(), E> return allows the ? operator. The integration tests live in the tests directory, and the documentation tests live in the doc comments.


Why the Rust testing exists

The built-in testing problem. The other languages require the separate test framework: the JUnit for the Java, the pytest for the Python, the Jest for the JavaScript. The Rust includes the testing in the toolchain, and the cargo test is the command. There is no dependency to add, no configuration to write, and no version to manage. The testing is the language feature, and the cargo test is the entry point.

The test-as-code problem. The Rust tests are the ordinary functions with the #[test] attribute. They live in the same module as the code, they use the same syntax, and they compile with the same compiler. The developer does not learn a new framework; the developer writes the functions. The test is the code, and the code is the test.

The private-function problem. The Rust unit tests can test the private functions, because the #[cfg(test)] mod tests is the child module, and the child modules can access the private items of the parent. The integration tests in the tests directory can only test the public API, because they are the external crates. The two kinds of tests cover the different levels, and the unit tests are the white-box, and the integration tests are the black-box.

The documentation problem. The documentation tests are the code examples in the doc comments. The cargo test extracts the examples, compiles them, and runs them. The example is the documentation and the test at the same time. The documentation that does not compile fails the test, and the example that does not work fails the test. The documentation is verified, and the example is the tested.

The cargo test output problem. The cargo test runs the tests in parallel, captures the output, and reports the pass and the fail. The --test-threads controls the parallelism, the --nocapture shows the output, the --ignored runs the ignored tests, and the --doc runs only the documentation tests. The command is the tool, and the flags are the control.

The trade-off. The Rust testing is the simple and the built-in, and the test organization is the convention. The unit tests in the same file are the convenient and the clutter. The integration tests in the separate directory are the clean and the public-API-only. The documentation tests are the free but require the well-written examples. The trade-off is between the convenience and the organization, and the conventions are the guide.


a. The unit tests

The unit tests live in the same file as the code, in the #[cfg(test)] mod tests module.

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_adds() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn it_handles_the_negatives() {
        assert_eq!(add(-2, -3), -5);
    }
}

The #[cfg(test)] includes the module only in the test build. The use super::* imports the parent module’s items, so the test can call the add function. The #[test] marks the test function, and the assert_eq! is the assertion .

The cargo test runs the tests:

cargo test

The output:

running 2 tests
test tests::it_adds ... ok
test tests::it_handles_the_negatives ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

The assertions. The assert! takes the boolean, and the assert_eq! and the assert_ne! take the two values:

#[test]
fn it_asserts() {
    assert!(true);
    assert_eq!(2 + 2, 4);
    assert_ne!(2 + 2, 5);
}

The macros accept the custom message as the additional argument:

assert_eq!(add(2, 3), 5, "the addition of the 2 and the 3 should be the 5");

The #[should_panic]. The test that must panic:

pub fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("the divisor cannot be the zero");
    }
    a / b
}

#[test]
#[should_panic(expected = "the divisor cannot be the zero")]
fn it_panics_on_the_zero() {
    divide(10, 0);
}

The #[should_panic] passes when the test panics, and the expected checks the panic message contains the substring .

The Result return. The test can return the Result<(), E>, and the ? operator works:

#[test]
fn it_parses() -> Result<(), Box<dyn std::error::Error>> {
    let value: i32 = "42".parse()?;
    assert_eq!(value, 42);
    Ok(())
}

The test passes when the Result is the Ok, and fails when it is the Err. The Result return is the modern, and the #[should_panic] is the legacy for the panic .

The private function testing. The unit test can test the private functions:

fn internal_add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_tests_the_private() {
        assert_eq!(internal_add(2, 3), 5);
    }
}

The internal_add is private, and the test module can access it because the child module can see the parent’s private items .

The #[ignore]. The expensive test is ignored by default:

#[test]
#[ignore = "the test is the slow"]
fn it_is_the_slow() {
    // the expensive setup
}

The cargo test skips the ignored, and the cargo test -- --ignored runs them .


b. The integration tests

The integration tests live in the tests directory, and each file is a separate crate. The test uses the library as the external consumer, and it can only access the public API.

my_project/
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ lib.rs
โ”œโ”€โ”€ tests/
โ”‚   โ””โ”€โ”€ integration_test.rs
โ””โ”€โ”€ Cargo.toml

The integration test:

// tests/integration_test.rs
use my_project::add;

#[test]
fn it_adds_from_the_outside() {
    assert_eq!(add(2, 3), 5);
}

The use my_project::add imports the public function from the library. The integration test cannot access the private items, because it is the external crate .

The cargo test runs the integration tests:

running 1 test
test it_adds_from_the_outside ... ok

The output shows the unit tests, the integration tests, and the documentation tests in the separate sections .

The shared test utilities. The tests/common/mod.rs is the shared module for the integration tests:

// tests/common/mod.rs
pub fn setup() -> TestContext {
    TestContext::new()
}

The test file uses the module:

// tests/integration_test.rs
mod common;

#[test]
fn it_uses_the_setup() {
    let context = common::setup();
    // ...
}

The common is the subdirectory with the mod.rs, and the cargo test does not treat the common/mod.rs as the separate test file .


c. The documentation tests

The documentation tests are the code examples in the doc comments. The cargo test extracts them, compiles them, and runs them.

/// The adds the two numbers.
///
/// # Examples
///
/// ```
/// let result = my_project::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

The triple-backtick block is the code example. The cargo test runs the example as the test, and the example fails when the assertion fails. The documentation and the test are the same .

The no_run attribute. The example that compiles but does not run:

/// ```no_run
/// let listener = std::net::TcpListener::bind("127.0.0.1:8080").unwrap();
/// ```

The no_run compiles the example but does not run it, and the ignore skips it entirely. The should_panic expects the panic, and the compile_fail expects the compile failure .

The ? operator in the example. The example with the ? needs the Result return:

/// ```
/// use std::error::Error;
/// fn main() -> Result<(), Box<dyn Error>> {
///     let value: i32 = "42".parse()?;
///     assert_eq!(value, 42);
///     Ok(())
/// }
/// ```

The example with the ? must return the Result, and the main function is the standard wrapper .

The cargo test --doc runs only the documentation tests:

cargo test --doc

The command runs the documentation tests and skips the unit and the integration tests .

The documentation test hiding. The # at the line start hides the line from the documentation but includes it in the test:

/// ```
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let value: i32 = "42".parse()?;
/// # Ok(())
/// # }
/// ```

The # lines are the boilerplate that the reader does not need to see, and the test includes them .


Complete Example Session

// ============================================
// PART 1: THE BASIC UNIT TEST
// ============================================

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_adds() {
        assert_eq!(add(2, 3), 5);
    }
}

// The cargo test runs the test.


// ============================================
// PART 2: THE ASSERTIONS
// ============================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_asserts() {
        assert!(true);
        assert_eq!(2 + 2, 4);
        assert_ne!(2 + 2, 5);
    }

    #[test]
    fn it_asserts_with_the_message() {
        assert_eq!(add(2, 3), 5, "the 2 + the 3 should be the 5");
    }
}


// ============================================
// PART 3: THE SHOULD_PANIC
// ============================================

pub fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("the divisor cannot be the zero");
    }
    a / b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic(expected = "the divisor cannot be the zero")]
    fn it_panics_on_the_zero() {
        divide(10, 0);
    }
}


// ============================================
// PART 4: THE RESULT RETURN
// ============================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_parses() -> Result<(), Box<dyn std::error::Error>> {
        let value: i32 = "42".parse()?;
        assert_eq!(value, 42);
        Ok(())
    }
}


// ============================================
// PART 5: THE PRIVATE FUNCTION
// ============================================

fn internal_add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_tests_the_private() {
        assert_eq!(internal_add(2, 3), 5);
    }
}


// ============================================
// PART 6: THE IGNORE
// ============================================

#[cfg(test)]
mod tests {
    #[test]
    #[ignore = "the test is the slow"]
    fn it_is_the_slow() {
        // the expensive setup
    }
}

// The cargo test skips the ignored.
// The cargo test -- --ignored runs them.


// ============================================
// PART 7: THE INTEGRATION TEST
// ============================================

// tests/integration_test.rs
use my_project::add;

#[test]
fn it_adds_from_the_outside() {
    assert_eq!(add(2, 3), 5);
}

// The cargo test runs the integration tests.


// ============================================
// PART 8: THE SHARED TEST UTILITIES
// ============================================

// tests/common/mod.rs
pub fn setup() -> TestContext {
    TestContext::new()
}

// tests/integration_test.rs
mod common;

#[test]
fn it_uses_the_setup() {
    let context = common::setup();
    // ...
}


// ============================================
// PART 9: THE DOCUMENTATION TEST
// ============================================

/// The adds the two numbers.
///
/// # Examples
///
/// ```
/// let result = my_project::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// The cargo test runs the documentation tests.


// ============================================
// PART 10: THE COMPLETE EXAMPLE
// ============================================

pub struct Calculator {
    memory: f64,
}

impl Calculator {
    pub fn new() -> Self {
        Calculator { memory: 0.0 }
    }

    pub fn add(&mut self, value: f64) -> f64 {
        self.memory += value;
        self.memory
    }

    pub fn divide(&mut self, value: f64) -> Result<f64, String> {
        if value == 0.0 {
            return Err("the division by the zero".to_string());
        }
        self.memory /= value;
        Ok(self.memory)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_adds() {
        let mut calc = Calculator::new();
        assert_eq!(calc.add(5.0), 5.0);
        assert_eq!(calc.add(3.0), 8.0);
    }

    #[test]
    fn it_divides() {
        let mut calc = Calculator::new();
        calc.add(10.0);
        assert_eq!(calc.divide(2.0), Ok(5.0));
    }

    #[test]
    fn it_rejects_the_zero_division() {
        let mut calc = Calculator::new();
        calc.add(10.0);
        assert!(calc.divide(0.0).is_err());
    }

    #[test]
    #[should_panic(expected = "the overflow")]
    fn it_panics_on_the_overflow() {
        panic!("the overflow");
    }
}

The ten parts show the basic unit test, the assertions, the #[should_panic], the Result return, the private function, the #[ignore], the integration test, the shared utilities, the documentation test, and the complete example.


Quick Reference

The Test Attributes

The attributeThe purpose
#[cfg(test)]The test-only module
#[test]The test function
#[should_panic]The test must panic
#[should_panic(expected = "...")]The panic message
#[ignore]The skipped test
#[ignore = "the reason"]The skip reason

The Assertion Macros

The macroThe purpose
assert!(condition)The boolean
assert!(condition, "msg")The boolean with the message
assert_eq!(a, b)The equality
assert_eq!(a, b, "msg")The equality with the message
assert_ne!(a, b)The inequality

The Test Commands

The commandThe purpose
cargo testRuns all the tests
cargo test -- --nocaptureShows the output
cargo test -- --ignoredRuns the ignored
cargo test -- --test-threads=1The single thread
cargo test --docThe documentation tests
cargo test test_nameThe specific test
cargo test --libThe unit tests only
cargo test --test nameThe integration test

The Documentation Test Attributes

The attributeThe purpose
``The test that compiles and runs
no_runThe compiles but does not run
ignoreThe skips
should_panicThe expects the panic
compile_failThe expects the compile failure
#The hidden line

The Test Organization

The locationThe access
src/lib.rs (the #[cfg(test)])The private and the public
tests/The public only
The doc commentsThe public

Best Practices

โœ… Do This:

// Use the #[cfg(test)] mod tests
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn it_works() { ... }
}                                                                         // โœ…
// Use the assert_eq! with the message
assert_eq!(result, expected, "the result should be the expected");        // โœ…
// Use the #[should_panic] with the expected
#[should_panic(expected = "the error message")]                           // โœ…
// Use the Result return for the fallible tests
fn it_works() -> Result<(), Box<dyn Error>> { ... }                       // โœ…
// Use the doc examples
/// ```
/// assert_eq!(add(2, 3), 5);
/// ```                                                                   // โœ…
// Use the integration tests for the public API
// The tests/ directory                                                   // โœ…

โŒ Don’t Do This:

// Don't forget the #[cfg(test)]
mod tests { ... }  // the test code in the release build                 // โŒ
// Don't use the unwrap in the test without the reason
let result = risky().unwrap();  // the unclear failure                   // โŒ
// Don't test the multiple things in one test
fn it_tests_everything() { ... }  // the unclear                        // โŒ
// Don't use the #[should_panic] without the expected
#[should_panic]  // the too broad                                       // โŒ
// Don't put the unit tests in the tests/ directory
// The tests/ is the integration, the unit is the src/                   // โŒ
// Don't forget the use super::*
mod tests { #[test] fn it() { add(2, 3); } } // the missing import       // โŒ

Common Pitfalls

The pitfallThe whyThe fix
The test not foundThe missing #[test]Add the attribute
The import errorThe missing use super::*Add the import
The test in the releaseThe missing #[cfg(test)]Add the attribute
The should_panic passes on the wrong panicThe missing expectedAdd the expected
The ? does not compileThe missing Result returnAdd the return type
The doc test failsThe wrong exampleFix the example
The integration test cannot access the privateThe external crateUse the unit test

Real-World Examples

1. The Basic Test

#[test]
fn it_adds() {
    assert_eq!(add(2, 3), 5);
}

2. The Should Panic

#[test]
#[should_panic(expected = "the zero")]
fn it_panics() {
    divide(10, 0);
}

3. The Result Return

#[test]
fn it_parses() -> Result<(), Box<dyn Error>> {
    let value: i32 = "42".parse()?;
    Ok(())
}

4. The Ignore

#[test]
#[ignore = "the slow"]
fn it_is_the_slow() { }

5. The Private Test

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn it_tests_the_private() { ... }
}

6. The Integration Test

// tests/integration_test.rs
use my_project::add;
#[test]
fn it_adds() { assert_eq!(add(2, 3), 5); }

7. The Doc Test

/// ```
/// assert_eq!(add(2, 3), 5);
/// ```

8. The No Run

/// ```no_run
/// let listener = TcpListener::bind("...").unwrap();
/// ```

9. The Compile Fail

/// ```compile_fail
/// let x: i32 = "not a number";
/// ```

10. The Complete

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_adds() { assert_eq!(add(2, 3), 5); }

    #[test]
    #[should_panic(expected = "the zero")]
    fn it_panics() { divide(10, 0); }
}

Visual

The Test Organization

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE TEST ORGANIZATION                                       โ”‚
โ”‚                                                              โ”‚
โ”‚  my_project/                                                 โ”‚
โ”‚  โ”œโ”€โ”€ src/                                                    โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ lib.rs                                              โ”‚
โ”‚  โ”‚       โ”œโ”€โ”€ the public functions                            โ”‚
โ”‚  โ”‚       โ”œโ”€โ”€ the private functions                           โ”‚
โ”‚  โ”‚       โ””โ”€โ”€ #[cfg(test)] mod tests { ... }  โ† the unit testsโ”‚
โ”‚  โ”‚                                                           โ”‚
โ”‚  โ”œโ”€โ”€ tests/                                                  โ”‚
โ”‚  โ”‚   โ”œโ”€โ”€ integration_test.rs  โ† the integration tests        โ”‚
โ”‚  โ”‚   โ””โ”€โ”€ common/                                             โ”‚
โ”‚  โ”‚       โ””โ”€โ”€ mod.rs           โ† the shared utilities         โ”‚
โ”‚  โ”‚                                                           โ”‚
โ”‚  โ””โ”€โ”€ Cargo.toml                                              โ”‚
โ”‚                                                              โ”‚
โ”‚  The cargo test runs:                                        โ”‚
โ”‚    1. The unit tests (the src/)                              โ”‚
โ”‚    2. The integration tests (the tests/)                     โ”‚
โ”‚    3. The documentation tests (the doc comments)             โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Test Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE TEST FLOW                                               โ”‚
โ”‚                                                              โ”‚
โ”‚  cargo test                                                  โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ”œโ”€โ”€ The unit tests                                        โ”‚
โ”‚    โ”‚     The #[cfg(test)] modules in the src/                โ”‚
โ”‚    โ”‚     The private and the public access                   โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ”œโ”€โ”€ The integration tests                                 โ”‚
โ”‚    โ”‚     The files in the tests/                             โ”‚
โ”‚    โ”‚     The public API only                                 โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ””โ”€โ”€ The documentation tests                               โ”‚
โ”‚          The code examples in the /// comments               โ”‚
โ”‚          The public API only                                 โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ–ผ                                                         โ”‚
โ”‚  The output:                                                 โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  running 5 tests                                     โ”‚    โ”‚
โ”‚  โ”‚  test tests::it_adds ... ok                          โ”‚    โ”‚
โ”‚  โ”‚  test tests::it_subtracts ... ok                     โ”‚    โ”‚
โ”‚  โ”‚  test it_adds_from_the_outside ... ok                โ”‚    โ”‚
โ”‚  โ”‚  test src/lib.rs - add (line 5) ... ok               โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  test result: ok. 5 passed; 0 failed                 โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Assertion Macros

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE ASSERTION MACROS                                        โ”‚
โ”‚                                                              โ”‚
โ”‚  assert!(condition);                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  The passes when the condition is the true.          โ”‚    โ”‚
โ”‚  โ”‚  The fails when the condition is the false.          โ”‚    โ”‚
โ”‚  โ”‚  The panics with the message.                        โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  assert_eq!(a, b);                                           โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  The passes when the a == the b.                     โ”‚    โ”‚
โ”‚  โ”‚  The fails when the a != the b.                      โ”‚    โ”‚
โ”‚  โ”‚  The message shows the a and the b.                  โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  assert_ne!(a, b);                                           โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  The passes when the a != the b.                     โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  The macros require the Debug and the PartialEq.             โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Documentation Test

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE DOCUMENTATION TEST                                      โ”‚
โ”‚                                                              โ”‚
โ”‚  /// The adds the two numbers.                               โ”‚
โ”‚  ///                                                         โ”‚
โ”‚  /// # Examples                                              โ”‚
โ”‚  ///                                                         โ”‚
โ”‚  /// ```                                                    โ”‚
โ”‚  /// let result = my_project::add(2, 3);                     โ”‚
โ”‚  /// assert_eq!(result, 5);                                  โ”‚
โ”‚  /// ```                                                    โ”‚
โ”‚  pub fn add(a: i32, b: i32) -> i32 { a + b }                 โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  The cargo test extracts the example, compiles it,   โ”‚    โ”‚
โ”‚  โ”‚  and runs it.                                        โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  The example passes โ†’ the doc test passes.           โ”‚    โ”‚
โ”‚  โ”‚  The example fails โ†’ the doc test fails.             โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  The documentation and the test are the same.        โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
#[cfg(test)]The test-only module
#[test]The test function
assert!The boolean assertion
assert_eq!The equality assertion
assert_ne!The inequality assertion
#[should_panic]The test must panic
#[ignore]The skipped test
Result<(), E>The fallible test return
The tests/ directoryThe integration tests
The doc commentsThe documentation tests
cargo testThe test runner

Key takeaways:

  • The Rust testing is built into the language. The cargo test is the command, the #[test] is the attribute, and there is no separate framework to install. The testing is the language feature .
  • The unit tests live in the #[cfg(test)] mod tests module. The module is included only in the test build, and the use super::* imports the parent’s items. The unit tests can access the private functions .
  • The assert!, the assert_eq!, and the assert_ne! are the assertions. The macros panic on the failure, and the panic message shows the values. The custom message is the additional argument .
  • The #[should_panic] marks the test that must panic. The expected attribute checks the panic message contains the substring. The test passes when the panic occurs .
  • The Result<(), E> return allows the ? operator. The test passes when the Result is the Ok, and fails when it is the Err. The Result return is the modern for the fallible tests .
  • The integration tests live in the tests/ directory. Each file is a separate crate, and the test can only access the public API. The tests/common/mod.rs is the shared utilities .
  • The documentation tests are the code examples in the doc comments. The cargo test extracts them, compiles them, and runs them. The documentation and the test are the same, and the example is the verified .
  • The cargo test runs all three kinds of tests. The unit, the integration, and the documentation. The --doc runs only the documentation, the --lib runs only the unit, and the --ignored runs the ignored .

Remember: The Rust testing is the built-in, the zero-dependency, and the first-class. The unit tests live in the #[cfg(test)] mod tests, the integration tests in the tests/ directory, and the documentation tests in the doc comments. The cargo test runs them all, and the assert!, the assert_eq!, and the assert_ne! are the assertions. The result is the tested code that ships with the confidence, which is the foundation of the reliable software.



Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!