32

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Constantine
  • 1,802
  • 3
  • 23
  • 37

3 Answers3

42

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Just to confirm, the "test-only module" solution up top only works with libraries and not with binaries, am I right ? (`extern crate the_library;` cannot be found if `the_library` has been created with `cargo new --bin`) – jean553 Dec 07 '17 at 11:31
  • @jean553 yes; only library crates can be reused by other crates. – Shepmaster Dec 07 '17 at 17:08
  • Hi @Shepmaster, great answer! It had been 4 years since you provided it. I can't find anything in documentation that would suggest how to share some helper methods between unit tests and integration tests. Has anything changed since your original answer? – Greg0ry Sep 22 '18 at 21:26
  • @Greg0ry *It has been 4 years since you provided it* — you and I are using different calendars. This answer was created **2017-06-14** (hover over the date next to "answered" for an ISO format date), a little over one year ago. But no, I don't believe so. – Shepmaster Sep 22 '18 at 21:29
  • Ah silly me... Anyways thanks again, in the end I went for test-only module. – Greg0ry Sep 22 '18 at 23:07
  • This does not work though if the main crate is set to ``crate-type = ["staticlib"]`` - in this case you won‘t be able to access the crate‘s types in the integration tests. – Philipp Ludwig Apr 17 '19 at 12:52
  • @PhilippLudwig there are 3 separate answers here. Are you saying that **all three** fail in this case? Additionally, what prevents you from creating both a staticlib and an rlib? That section of Cargo.toml is an array for a reason... – Shepmaster Apr 17 '19 at 12:55
  • 4
    [Another comment here](https://stackoverflow.com/questions/44539729/what-is-an-idiomatic-way-to-have-shared-utility-functions-for-integration-tests#comment92938231_44541071) claims the "shared crate" approach will prevent you from publishing the main crate (unless you also publish the utilities crate). If that's correct, it ought to be mentioned in the answer I think.. – Nickolay May 02 '19 at 17:32
  • @Shepmaster I wasn’t aware that you could build more than one library type at the same time. – Philipp Ludwig May 06 '19 at 11:58
  • Should this be updated with [cargo workspaces](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html)? – Jonathan Tran Sep 04 '22 at 01:11
  • @JonathanTran workspaces don't materially change any of the calculus here. The workspace will only reduce the amount of times you have to compile a given shared crate. – Shepmaster Sep 12 '22 at 17:33
7

You could add those utility-functions to a pub-module inside your main crate and use the #[doc(hidden)] or #![doc(hidden)] attribute to hide them from the docs-generator. Extra comments will guide the reader to why they are there.

user2722968
  • 13,636
  • 2
  • 46
  • 67
  • 2
    For crates that should be released, this is the better solution: crates.io will refuse path dependencies, so you either have to publish your util crate, or put them into an undocumented module. I think the latter is preferable. – antifuchs Oct 26 '18 at 23:39
1

Whilst this doesn't help for benchmarks, I came here looking for a way to do this with multiple integration tests, and later found that you can do the following for integration tests:

Modules with common code follow the ordinary modules rules, so it's ok to create common module as tests/common/mod.rs.

Source: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html

PiRocks
  • 1,708
  • 2
  • 18
  • 29