2017-11-02 7 views
1

最初のRustプロジェクトのために私はおそらくそれを使用しているので、私はterminalビルダーパターンのメソッドをテストするためのきれいな方法を見つけることができないので、ユーザ入力を取得し答えを返します。Rustでユーザー入力が必要な関数を使用する関数をより洗練された方法でテストできますか?

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を使用してユーザーに入力を促し機能のためのテストを書くことが許さdependency injection発見し解決策を探しています。私はQuestion::new("Continue?").confirm()というテストごとに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); 
} 

これがある限り、私は単一スレッドのみでテストを実行するように動作していない、だけでなく、もはや私は本当のユーザーをテストすることができます入力機能。このような小さなクレートの問題ではありませんが、非常に面倒です。利用可能なテストライブラリからこれを行う方法はありませんでした。

+0

*入力ごとに関数への入力を変更することはできません* - それが意味することを明確にすることはできますか? – Shepmaster

+0

'reader'、' writer'、 'question'はすべて' confirm() '関数の中で' prompt_user() 'に渡されるので、' prompt_user() '関数をテストするときのように入力を操作できません自体。だから私は 'confirm()'を含むテストを自動化することができません。 –

+1

それでは、引数を持たない 'confirm'は依存関係注入をサポートしていないと言っていますか?もしそうなら、私は私たちが何を手伝ってくれるか分かりません。はい、依存関係を注入する方法が必要ですが、引数は最も簡単ですが、構造体フィールドを持つこともできます。あなたが必要とする依存関係を注入するために 'confirm'の引数を持つことを妨げるものは何ですか? – Shepmaster

答えて

3

Stack Overflow question you linkedで述べたように、あなたがテスト容易化したい場合、あなたは一般的にハードワイヤリング外部依存関係(別名I/O)を避ける必要があります。

  • ディスクアクセス、
  • 端末アクセス、
  • ネットワークアクセスが、
  • データベースアクセス
  • 時間アクセス。 、私は依存性注入を使用することをお勧めしますすべてのこのような場合には

  • 許可されたアクションを(!、YAGNIを無理をしないでください)記述するためにクリーンなインターフェース(特性)を作成し、
  • "テスト"の使用のためのインターフェイスの "モック"を実装する
  • は、 "生産"の使用のためのインターフェイスを実装しています。
  • その後

、書き込み:

  • このリソースへのアクセスを必要とする関数を、引数として渡し、
  • このリソースへのアクセスを必要とする方法を、どちらかの引数として、またはそれを渡しますオブジェクトのコンストラクタ

最後に、メインのプロダクション依存関係をインスタンス化し、そこから転送します。


トリックではなく、扱い:

  • むしろ各関数の引数のヒープを通過するよりも、そのようなすべてのインターフェースを含んEnvironment構造を作成するのに有用であり得ます。しかし
  • 、それを明確にどのような彼らが使用するために、明示的にそれらを取る必要がありますのみ1/2つのリソース(複数可)を必要とする機能は、それが役に立つタイムスタンプではなく、それが中に得られるからクロックを渡すために、私を発見しました過去... now()への複数の呼び出しが時間の経過とともに異なる結果を返すことがあるからです。
+0

+1。私は、以前は/ r/rustのコメントによってDIが不可能であったという印象を与えられましたが、それがまだ可能であることを喜ばしました。 – user9993

+3

@ user9993:Rustに* Dependency Injection Framework *はありませんが、依然として依存関係を手動で渡すことはできます。 Rc >またはそのマルチスレッド対応のものを使用して、その特性を保持することをお勧めします。外部依存関係はCPU時間と比較して遅いですが、余分なメモリ割り当てと仮想呼び出しはほとんどありませんレーダー上の瞬間です。 –

関連する問題