I am interested in any kind of possible improvement to this method of testing a Rust program with user interaction in an end-to-end manner (simulating user input and asserting a certain program output given a certain user input), I also added an example usage of this framework with a number guessing game for clarity.
This method works by passing BufRead
and Write
functions to the functions, that can be either be used with the std::io::stdin()
and std::io::stdout()
values in the normal program or mocked when testing. This testing method works but complicates the arguments and the usage of the function to be tested. Is there any other method that can be applied without making changes to the function to be tested?
use std::io::{BufRead, Write, Cursor};
use rand::Rng;
fn play_game<R: BufRead, W: Write>(input: &mut R, output: &mut W, num: u32) -> std::io::Result<()> {
writeln!(output, "Guess the number!")?;
writeln!(output, "Please input your guess.")?;
let secret_number = num;
loop {
let mut guess = String::new();
input.read_line(&mut guess)?;
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
writeln!(output, "Invalid input. Please enter a number.")?;
continue;
}
};
writeln!(output, "You guessed: {guess}")?;
match guess.cmp(&secret_number) {
std::cmp::Ordering::Less => writeln!(output, "Too small!")?,
std::cmp::Ordering::Greater => writeln!(output, "Too big!")?,
std::cmp::Ordering::Equal => {
writeln!(output, "You win!")?;
break;
}
}
writeln!(output, "Please input your guess.")?;
}
Ok(())
}
fn main() -> std::io::Result<()> {
let stdin = std::io::stdin();
let mut stdout = std::io::stdout();
let secret_number = rand::thread_rng().gen_range(1..101);
play_game(&mut stdin.lock(), &mut stdout, secret_number)?;
Ok(())
}
#[test]
fn test_play_game() {
let input = b"50\n75\n60\n55\n52\n53";
let expected_output = "Guess the number!\n\
Please input your guess.\n\
You guessed: 50\n\
Too small!\n\
Please input your guess.\n\
You guessed: 75\n\
Too big!\n\
Please input your guess.\n\
You guessed: 60\n\
Too big!\n\
Please input your guess.\n\
You guessed: 55\n\
Too big!\n\
Please input your guess.\n\
You guessed: 52\n\
Too small!\n\
Please input your guess.\n\
You guessed: 53\n\
You win!\n";
let mut input_stream = Cursor::new(input);
let mut output_stream = Cursor::new(Vec::new());
play_game(&mut input_stream, &mut output_stream, 53).unwrap();
let actual_output = String::from_utf8(output_stream.into_inner()).unwrap();
assert_eq!(actual_output, expected_output);
}