Sling Academy
Home/Rust/Testing Private vs Public Functions in Rust Modules

Testing Private vs Public Functions in Rust Modules

Last updated: January 06, 2025

When developing with Rust, a powerful systems programming language, understanding how to effectively test both private and public functions within your modules can dramatically improve your code reliability, maintainability, and robustness. Rust's module system inherently influences how we write our tests, given its encapsulation and visibility rules. This article explores how to test both private and public functions in Rust, backed by practical examples.

Understanding Module Visibility

In Rust, a module defines its own scope for functions, types, and variables, controlling their visibility with keywords such as pub. By default, everything within a module is private, visible only within the module itself. Declaring an item with the pub keyword makes it public, meaning it can be freely accessed by other modules.

Testing Public Functions

Public functions are written and tested much like in any other language. Here’s a small example to demonstrate:

// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

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

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

In this snippet, we define a simple public function add that we want to test. The test resides within the same file, under the #[cfg(test)] attribute, distinctly marking it as a test module irrelevant to release builds.

Testing Private Functions

Testing private functions might initially appear challenging since they lack visibility outside their home module. However, Rust's structure allows even private functions to be peer-tested, provided the tests are within the module boundary. Consider the following example:

// src/lib.rs
fn increment(x: i32) -> i32 {
    x + 1
}

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

    #[test]
    fn test_increment() {
        assert_eq!(increment(3), 4);
    }
}

Although increment is a private function, it’s accessible within the test as tests is a nested module of its parent, making all parent module's items accessible regardless of their visibility.

Best Practices for Testing Private Functions

While testing private functions is possible, consider focusing your tests on public-facing functions. These functions interact directly with your program's exterior aspects or other modules and should thus remain the primary candidates for comprehensive testing.

However, private function testing might prove valuable in more technical, critical solutions and libraries, where implementation accuracy is paramount. Remember, such tests should be aimed at checking functionalities that aren't aptly tested through public interfaces.

Running the Tests

Leveraging Rust’s integrated testing framework through cargo test, make sure your tests are adequately compiled and executed.

$ cargo test

This command automates discovery and execution of functions marked with the #[test] attribute, delivering a summary which indicates how many tests passed, failed, or were ignored.

In Conclusion

Testing in Rust effectively leverages the power of the language’s strong typing and modular system, ensuring that both public and private functionalities meet expected compliance and assertions. Rust offers a developer-friendly environment to craft your tests directly inline or within the same module, fostering ease of refactoring and consistency checks continually. With diligent testing of both interfaces and interrelated inner mechanics, maintaining sophisticated, resilient Rust applications becomes a straightforward endeavor.

Next Article: Using #[should_panic] in Rust for Expected Failures

Previous Article: Verifying Error Handling and Panics in Rust Tests

Series: Testing in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior