是否有更简洁的方法来测试使用需要用户在 Rust 中输入的函数的函数? [英] Is there a cleaner way to test functions that use functions that require user input in Rust?

查看:62
本文介绍了是否有更简洁的方法来测试使用需要用户在 Rust 中输入的函数的函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为我的第一个 Rust 项目编写一个 CLI 问题询问库,因为我可能会使用无论如何,我找不到一种干净的方法来测试构建器模式的 terminal 方法,该方法使用配置获取用户输入并返回答案.

I am writing a CLI question asking library for my first Rust project since I will probably be using it anyway, and I cannot find a clean way to test the terminal method of the builder pattern, which using the configuration gets user input and returns an answer.

pub fn confirm(&mut self) -> Answer {
    self.yes_no();
    self.build_prompt();
    let prompt = self.prompt.clone();
    let valid_responses = self.valid_responses.clone().unwrap();
    loop {
        let stdio = io::stdin();
        let input = stdio.lock();
        let output = io::stdout();
        if let Ok(response) = prompt_user(input, output, &prompt) {
            for key in valid_responses.keys() {
                if *response.trim().to_lowercase() == *key {
                    return valid_responses.get(key).unwrap().clone();
                }
            }
            self.build_clarification();
        }
    }
}

寻找解决方案时我发现了依赖注入使用 为提示用户输入的函数编写测试光标.它不允许我为 Question::new("Continue?").confirm() 的每个测试更改对 confirm() 函数的用户输入,所以我尝试使用条件编译,并得出以下结论.

Looking for a solution I discovered dependency injection which allowed me to write tests for the function that prompts the user for input using Cursor. It does not let me change the user input to the confirm() function for each test of Question::new("Continue?").confirm() though so I tried using conditional compilation, and came up with the following.

#[cfg(not(test))]
fn prompt_user<R, W>(mut reader: R, mut writer: W, question: &str) -> Result<String, std::io::Error>
where
    R: BufRead,
    W: Write,
{
    write!(&mut writer, "{}", question)?;
    let mut s = String::new();
    reader.read_line(&mut s)?;
    Ok(s)
}

#[cfg(test)]
fn prompt_user<R, W>(mut reader: R, mut writer: W, question: &str) -> Result<String, std::io::Error>
where
    R: BufRead,
    W: Write,
{
    use tests;
    Ok(unsafe { tests::test_response.to_string() })
}

tests 模块中,我使用了一个全局变量:

And in the tests module I use a global variable:

pub static mut test_response: &str = "";

#[test]
fn simple_confirm() {
    unsafe { test_response = "y" };
    let answer = Question::new("Continue?").confirm();
    assert_eq!(Answer::YES, answer);
}

只要我只用单线程运行测试,这就能工作,但也不再允许我测试真正的用户输入功能.对于这么小的板条箱来说并不是问题,但它非常凌乱.我没有从任何可用的测试库中看到任何解决方案.

This works as long as I only run tests with a single thread, but also no longer allows me to test the real user input function. Not really a problem for such a small crate but it is very messy. I did not see any solutions to do this from any available testing libraries.

推荐答案

您链接的堆栈溢出问题,如果您想要可测试性,通常应该避免硬连接外部依赖项(又名 I/O):

As mentioned in the Stack Overflow question you linked, you should generally avoid hard-wiring external dependencies (a.k.a. I/O) if you want testability:

  • 磁盘访问,
  • 终端访问,
  • 网络访问,
  • 数据库访问,
  • 时间访问.

在所有这些情况下,我建议使用依赖注入:

In all such cases, I recommend using Dependency Injection:

  • 创建一个干净的界面(特征)来描述允许的操作(不要过度,YAGNI!),
  • 实现用于生产"使用的接口,背后有真正的外部依赖,
  • 实现界面的模拟"以供测试"使用.

然后,在写作时:

  • 需要访问此资源的函数,将其作为参数传递,
  • 需要访问此资源的方法,将其作为参数或在对象的构造函数中传递.

最后,在 main 中实例化生产依赖项,并从那里转发它们.

Finally, instantiate the production dependencies in main, and forward them from there.

技巧,而不是款待:

  • 创建一个包含所有这些接口的 Environment 结构可能很有用,而不是将成堆的参数传递给每个函数;然而,只需要一个/两个资源的函数应该明确地使用这些资源,以明确它们的用途,
  • 我发现传递时间戳而不是过去从中获取它的时钟很有用...只是因为多次调用现在() 可能会随着时间的推移返回不同的结果.
  • It may be useful to create an Environment structure which contains all such interfaces, rather than passing heaps of arguments to each function; however functions which only require one/two resource(s) should take those explicitly to make it clear what they use,
  • I have found it useful to pass the timestamp rather than the clock from which it is obtained in the past... just because multiple calls to now() may return different results as the time passes.

这篇关于是否有更简洁的方法来测试使用需要用户在 Rust 中输入的函数的函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆