Search code examples
testingrustbenchmarkingrust-cargo

What is an idiomatic way to have shared utility functions for integration tests and benchmarks?


I have Rust project with both integration tests (in the /tests dir) and benchmarks (in the /benches dir). There are a couple of utility functions that I need in tests and benches, but they aren't related to my crate itself, so I can't just put them in the /utils dir.

What is idiomatic way to handle this situation?


Solution

  • Create a shared crate (preferred)

    As stated in the comments, create a new crate. You don't have to publish the crate to crates.io. Just keep it as a local unpublished crate inside your project and mark it as a development-only dependency.

    This is best used with version 2 of the Cargo resolver. For better performance, consider using a Cargo workspace.

    .
    ├── Cargo.toml
    ├── src
    │   └── lib.rs
    ├── tests
    │   └── integration.rs
    └── utilities
        ├── Cargo.toml
        └── src
            └── lib.rs
    

    Cargo.toml

    # ...
    
    [dev-dependencies]
    utilities = { path = "utilities" }
    

    utilities/src/lib.rs

    pub fn shared_code() {
        println!("I am shared code");
    }
    

    tests/integration.rs

    extern crate utilities;
    
    #[test]
    fn a_test() {
        utilities::shared_code();
    }
    

    A test-only module

    You could place a module inside your crate that is only compiled when a specific feature is passed. This is the same concept used for unit tests. This has the advantage that it can access internals of your library code. It has the disadvantage that you need to pass the flag each time you run the code.

    This is best used with version 2 of the Cargo resolver.

    Cargo.toml

    # ...
    
    [features]
    test-utilities = []
    

    src/lib.rs

    #[cfg(feature = "test-utilities")]
    pub mod test_utilities {
        pub fn shared_code() {
            println!("I'm inside the library")
        }
    }
    

    tests/integration.rs

    extern crate the_library;
    
    #[test]
    fn a_test() {
        the_library::test_utilities::shared_code();
    }
    

    execution

    cargo test --features=test-utilities
    

    This is best used with version 2 of the Cargo resolver.

    Use a module from an arbitrary file path

    This is just ugly to me, and really goes out of the normal path.

    utilities.rs

    pub fn shared_code() {
        println!("This is just sitting out there");
    }
    

    tests/integration.rs

    #[path = "../utilities.rs"]
    mod utilities;
    
    #[test]
    fn a_test() {
        utilities::shared_code();
    }
    

    See also: