или его многопоточный аналог для хранения характеристик: внешние зависимости в любом случае медленнее по сравнению с временем ЦП, выделение дополнительной памяти и виртуальные вызовы едва ли будут заметны на радаре.

уCLI задает вопрос библиотеке для моего первого проекта Rust, так как я, вероятно, буду использовать его в любом случае, и я не могу найти чистый способ проверитьterminal метод шаблона построения, который с помощью конфигурации получает пользовательский ввод и возвращает ответ.

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();
        }
    }
}

В поисках решения, которое я обнаружилвнедрение зависимости что позволило мне написать тесты для функции, которая предлагает пользователю ввод с помощьюCursor, Это не позволяет мне изменить пользовательский ввод наconfirm() функция для каждого тестаQuestion::new("Continue?").confirm() хотя, поэтому я попытался использовать условную компиляцию, и придумал следующее.

#[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 Модуль Я использую глобальную переменную:

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);
}

Это работает до тех пор, пока я запускаю тесты только с одним потоком, но также не позволяет мне проверять реальную функцию пользовательского ввода. Не такая уж проблема для такого маленького ящика, но он очень грязный. Я не видел никаких решений для этого из каких-либо доступных библиотек тестирования.

 Shepmaster02 нояб. 2017 г., 15:39
Это не позволяет мне изменить вход для функции для каждого входа - Можете ли вы уточнить, что вы подразумеваете под этим?
 Shepmaster02 нояб. 2017 г., 15:59
Итак, вы говорите, чтоconfirm, который не имеет аргументов, не поддерживает внедрение зависимостей? Если так, я не уверен, какую помощь мы можем оказать. Да, у вас должен быть какой-то способ внедрения зависимостей, аргументы самые простые, но вы также можете иметь поля struct. Что мешает вам поспоритьconfirm ввести зависимости, которые вам нужны?
 datenstrom02 нояб. 2017 г., 16:07
Я не хотелаconfirm иметь какие-либо аргументы просто как вопрос общественного дизайна интерфейса. Я даже не думал об использовании структурных полей для аргументовprompt_user вconfirm хотя. Я думаю, что это может сработать, и позвольте мне изменить источник ввода во время тестирования.
 datenstrom02 нояб. 2017 г., 15:46
Потому чтоreader, writer, а такжеquestion все переданыprompt_user() внутриconfirm() Функция Я не могу манипулировать вводом, как я могу при тестированииprompt_user() функционировать сам. Поэтому я не могу автоматизировать тесты с участиемconfirm().

Ответы на вопрос(1)

Решение Вопроса

Вопрос переполнения стека, который вы связалиобычно вам следует избегать жестких внешних зависимостей (например, ввода-вывода), если вы хотите тестируемости:

доступ к диску,терминальный доступ,доступ к сети,доступ к базе данных,время доступ.

Во всех таких случаях я рекомендую использоватьВнедрение зависимости:

создать чистый интерфейс (черту) для описания разрешенных действий (не переусердствуйте, YAGNI!),реализовать интерфейс для «производственного» использования с реальной внешней зависимостью,реализовать «макет» интерфейса для «тестового» использования.

Затем при написании:

функция, которая требует доступа к этому ресурсу, передать его в качестве аргумента,метод, который требует доступа к этому ресурсу, передайте его в качестве аргумента или в конструкторе объекта.

Наконец, создайте производственные зависимости в main и отправьте их оттуда.

Трюки, а не угощение:

Это может быть полезно для созданияEnvironment структура, которая содержит все такие интерфейсы, а не передает кучу аргументов каждой функции; однако функции, которые требуют только одного / двух ресурсов, должны явно их использовать, чтобы было понятно, что они используют,Я считаю полезным передатьотметка времени а неЧасы из которого это получается в прошлом ... только потому, что несколько вызововnow() может вернуть разные результаты с течением времени.
 user999302 нояб. 2017 г., 17:44
+1. У меня сложилось впечатление, что DI невозможен в Rust ранее из-за комментариев, сделанных на / r / rust, приятно видеть, что это все еще возможно.
 Matthieu M.02 нояб. 2017 г., 18:21
@ user9993: ржавчины нетСтруктура внедрения зависимостей, но все еще возможно передать зависимости вручную. Следующим препятствием является универсальность и ссылки, я советую использовать либоRc<RefCell<Trait>> или его многопоточный аналог для хранения характеристик: внешние зависимости в любом случае медленнее по сравнению с временем ЦП, выделение дополнительной памяти и виртуальные вызовы едва ли будут заметны на радаре.

Ваш ответ на вопрос