Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to move tests into a separate file for binaries in Rust's Cargo?

I created a new binary using Cargo:

cargo new my_binary --bin

A function in my_binary/src/main.rs can be used for a test:

fn function_from_main() {
    println!("Test OK");
}

#[test]
fn my_test() {
    function_from_main();
}

And cargo test -- --nocapture runs the test as expected.

What's the most straightforward way to move this test into a separate file, (keeping function_from_main in my_binary/src/main.rs)?

I tried to do this but am not sure how to make my_test call function_from_main from a separate file.

like image 597
ideasman42 Avatar asked Aug 17 '16 11:08

ideasman42


4 Answers

The Rust Programming Language has a chapter dedicated to testing which you should read to gain a baseline understanding.


It's common to put unit tests (tests that are more allowed to access internals of your code) into a test module in each specific file:

fn function_from_main() {
    println!("Test OK");
}

#[cfg(test)]
mod test {
    use super::*;
    
    #[test]
    fn my_test() {
        function_from_main();
    }
}

Modules can be moved to new files, although this is uncommon for the unit test module:

main.rs

fn function_from_main() {
    println!("Test OK");
}

#[cfg(test)]
mod test;

test.rs

use super::*;

#[test]
fn my_test() {
    function_from_main();
}

See Separating Modules into Different Files for detailed information on how files and modules map to each other.


The more common case for tests in a separate file are integration tests. These are also covered in the book by a section devoted to tests outside of the crate. These types of tests are well-suited for exercising the code as a consumer of your code would.

That section of the documentation includes an introductory example and descriptive text:

We create a tests directory at the top level of our project directory, next to src. Cargo knows to look for integration test files in this directory. We can then make as many test files as we want to in this directory, and Cargo will compile each of the files as an individual crate.

Let’s create an integration test. With the code in Listing 11-12 still in the src/lib.rs file, make a tests directory, create a new file named tests/integration_test.rs, and enter the code in Listing 11-13:

Filename: tests/integration_test.rs

use adder;

#[test]
fn it_adds_two() {
    assert_eq!(4, adder::add_two(2));
}

Listing 11-13: An integration test of a function in the adder crate

We’ve added use adder at the top of the code, which we didn’t need in the unit tests. The reason is that each test in the tests directory is a separate crate, so we need to bring our library into each test crate’s scope.

Note that the function is called as adder::add_two. Further details about Rust's module system can be found in the Packages, Crates, and Modules chapter.

Since these tests exercise your crate as a user would, if you want to test a binary, you should be executing the binary. Crates like assert_cmd can help reduce the pain of this type of test.

In other cases, you should break your large binary into a large library and a small binary. You can then write integration tests for the public API of your library.

See also:

  • Rust package with both a library and a binary?
like image 197
Shepmaster Avatar answered Oct 18 '22 20:10

Shepmaster


If you have a module foo.rs and want to place your unit tests next to it in a file called foo_test.rs, you'll find that this isn't always the place that Rust will look for a child module.

You can use the #[path] attribute to specify the location of the file corresponding to the module:

#[cfg(test)]
#[path = "./foo_test.rs"]
mod foo_test;

This is explained in the blog post Better location for unit tests in Rust.

like image 41
mmai Avatar answered Oct 18 '22 19:10

mmai


You're right; function_from_main is inaccessible outside of main.rs.

You need to create an src/lib.rs and move the functions you want to test piecemeal. Then you'll be able to use extern crate my_binary; from your test module, and have your functions appear under the my_binary namespace.

like image 3
Tobu Avatar answered Oct 18 '22 19:10

Tobu


I do believe you should follow the advice of reading the Rust book chapter on testing, however, that still won't quite answer your question, how to separate test and source files.

So say you have a lib.rs source file and wanted a test_lib.rs file. To do this all you need is:

In your lib.rs:

mod test_lib;

// rest of source

Then in your test_lib.rs:

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

#[test]
fn test1() {
    // test logic 
}

// more tests

Then, both you and cargo test should be happy.

like image 1
Rock Boynton Avatar answered Oct 18 '22 20:10

Rock Boynton