Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a variable with a scope/lifecycle for all test functions in a Rust test?

I have a test that initializes a variable before diving into the detail of the test, and I want to make a second test with the same variable, and not duplicate the initialization code:

#[test]
fn test_one() {
    let root = Path::new("data/");
    // the rest of the test
}
#[test]
fn test_two() {
    let root = Path::new("data/");
    // the rest of the test
}

I don't think static or const would do it because the size would not be known up front, though PathBuf.from(path) might make that OK, except that initialization expressions for static/const vars cannot be too complex.

I've seen lazy_static, but have not seen any examples of its use in tests. This after seeing the compiler error with "an extern crate loading macros must be at the crate root", which online searching tells me is something about being outside main(), but tests don't have main functions.

In Java, I would define the variable then initialize it in a setup() method, but I can't see examples of that online for Rust.

like image 850
paul_h Avatar asked Sep 23 '17 10:09

paul_h


People also ask

How do you test a function in Rust?

At its simplest, a test in Rust is a function that's annotated with the test attribute. Attributes are metadata about pieces of Rust code; one example is the derive attribute we used with structs in Chapter 5. To change a function into a test function, add #[test] on the line before fn .

What is #[ CFG test )]?

The #[cfg(test)] annotation on the tests module tells Rust to compile and run the test code only when you run cargo test , not when you run cargo build .

Does Rust use static scoping?

In Rust global variables are declared with the static keyword. In C there is a difference between static variables (with translation unit scope) and non-static global variables (with truly global scope).


1 Answers

Foremost, remember that Rust tests are run in parallel. This means that any shared setup needs to be thread-safe.

and not duplicate the initialization code

You do it the same way you avoid duplicating any other code: create a function, create a type, create traits, etc.:

use std::path::PathBuf;

fn root() -> PathBuf {
    PathBuf::from("data/")
}

#[test]
fn test_one() {
    let root = root();
    // the rest of the test
}

#[test]
fn test_two() {
    let root = root();
    // the rest of the test
}

In Java I would define the variable, then initialize it in a setup() method

Instead, make a struct called Setup containing all those variables and construct it as the first thing in each test:

use std::path::{Path, PathBuf};

struct Setup {
    root: PathBuf,
}

impl Setup {
    fn new() -> Self {
        Self {
            root: PathBuf::from("data/"),
        }
    }
}

#[test]
fn test_one() {
    let setup = Setup::new();
    let root: &Path = &setup.root;
    // the rest of the test
}

#[test]
fn test_two() {
    let setup = Setup::new();
    let root: &Path = &setup.root;
    // the rest of the test
}

but have not seen any examples of [lazy-static] use in tests

That's because there is no different way to use it in tests, it's just code:

#[macro_use]
extern crate lazy_static;

use std::path::Path;

lazy_static! {
    static ref ROOT: &'static Path = Path::new("data/");
}

#[test]
fn test_one() {
    let root = *ROOT;
    // the rest of the test
}

#[test]
fn test_two() {
    let root = *ROOT;
    // the rest of the test
}

See also:

  • How to initialize the logger for integration tests?

Very specifically for your case, it's very rare that you need exactly a Path, since a string slice implements AsRef<Path>. Said another way, most places that accept a Path accept a &str:

static ROOT: &str = "data/";

#[test]
fn test_one() {
    let root = ROOT;
    // the rest of the test
}

#[test]
fn test_two() {
    let root = ROOT;
    // the rest of the test
}
like image 121
Shepmaster Avatar answered Sep 21 '22 12:09

Shepmaster