简体   繁体   中英

Creating an `std::env::Args` iterator for testing

Is there a way in Rust to create a std::env::Args from a Vec<String> in order to use it in a #[test] function?

I wish to test a function that gets a std::env::Args as an argument, but I don't know how to create such an object with a list of arguments I supply for the test.

I wasn't able to figure this one out from the docs, the source nor from Google searches.

The fields of std::env::Args are not documented, and there doesn't appear to be a public function to create one with custom fields. So, you're outta luck there.

But since it's just " An iterator over the arguments of a process, yielding a String value for each argument " your functions can take a String iterator or Vec without any loss of functionality or type safety. Since it's just a list of String s, it doesn't make much sense to arbitrarily limit your functions to strings which happen to come from the command line.

Looking through Rust's own tests , that's just what they do. There's a lot of let args: Vec<String> = env::args().collect();

There's even an example in rustbuild where they strip off the name of the program and just feed the list of arguments.

use std::env;

use bootstrap::{Config, Build};

fn main() {
    let args = env::args().skip(1).collect::<Vec<_>>();
    let config = Config::parse(&args);
    Build::new(config).build();
}

And bootstrap::Config::parse() looks like so:

impl Config {
    pub fn parse(args: &[String]) -> Config {
        let flags = Flags::parse(&args);
        ...

I'm not a Rust expert, but that seems to be how the Rust folks handle the problem.

@Schwern's answer is good and it led me to this simpler version. Since std::env::Args implements Iterator with Item = String you can do this:

use std::env;

fn parse<T>(args: T)
where
    T: Iterator<Item = String>,
{
    for arg in args {
        // arg: String
        print!("{}", arg);
    }
}

fn main() {
    parse(env::args());
}

To test, you provide parse with an iterator over String :

#[test]
fn test_parse() {
   let args = ["arg1", "arg2"].iter().map(|s| s.to_string());
   parse(args);
}

I've wrote a little macro to make this easier, based on @Rossman's answer (and therefore also based on @Schwern's answer ; thanks go to both):

macro_rules! make_string_iter {
    ($($element: expr), *) => {
        {
            let mut v = Vec::new();
            $( v.push(String::from($element)); )*
            v.into_iter()
        }
    };
}

It can be used in that way:

macro_rules! make_string_iter {
    ($($element: expr), *) => {
        {
            let mut v = Vec::new();
            $( v.push(String::from($element)); )*
            v.into_iter()
        }
    };
}

// We're using this function to test our macro
fn print_args<T: Iterator<Item = String>>(args: T) {
    for item in args {
        println!("{}", item);
    }
}

fn main() {

    // Prints a, b and c
    print_args(make_string_iter!("a", "b", "c"))
}

Or try it out on the Rust Playground .

I'm not (yet) an expert in rust, any suggestions are highly welcome :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM