web-dev-qa-db-ja.com

スキャナーを使用したユーザー入力のjunitテスト

Scannerクラスを使用して入力を受け取るクラスのメソッドをテストする必要があります。

package com.math.calculator;

import Java.util.Scanner;

public class InputOutput {

    public String getInput() {
        Scanner sc = new Scanner(System.in);
        return sc.nextLine();
    }
}

JUnitを使用してテストしたいのですが、方法がわかりません。

次のコードを使用してみましたが、機能しません。

package com.math.calculator;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class InputOutputTest {

    @Test
    public void shouldTakeUserInput() {
        InputOutput inputOutput= new InputOutput();

        assertEquals("add 5", inputOutput.getInput());
    }
}

Mockitoでも試してみたい(mock ...を使用するとき... then thenReturn)が、その方法がわからない。

11
Ross

System.setIn()メソッドを使用してSystem.inストリームを変更できます。

これを試して、

@Test
public void shouldTakeUserInput() {
    InputOutput inputOutput= new InputOutput();

    String input = "add 5";
    InputStream in = new ByteArrayInputStream(input.getBytes());
    System.setIn(in);

    assertEquals("add 5", inputOutput.getInput());
}

System.inフィールドを変更しました。 System.inは基本的にInputStreamから読み取るconsoleです(つまり、コンソールでの入力です)。しかし、それを変更して、代わりにシステムに提供されたinputstreamから読み取らせるだけです。そのため、コンソールからは読み込まれず、提供された入力ストリームから読み込まれます。

20
Codebender

システムルール ライブラリのTextFromStandardInputStreamルールを使用して、コマンドラインインターフェイスの明確なテストを作成できます。

public void MyTest {
  @Rule
  public final TextFromStandardInputStream systemInMock
    = emptyStandardInputStream();

  @Test
  public void shouldTakeUserInput() {
    systemInMock.provideLines("add 5", "another line");
    InputOutput inputOutput = new InputOutput();
    assertEquals("add 5", inputOutput.getInput());
  }
}
5
Stefan Birkner

switching System.in に加えて、Codebenderでも言及されているように、リファクタリングを検討して、getInput()が完全なgetInput(Scanner)メソッドへの1行の呼び出しになるようにします。独自のScanner("your\ntest\ninput\n")を作成することで簡単にテストできます。テストのためにフィールドを上書きするなど、スキャナーの依存関係を挿入する方法は他にもいくつかありますが、メソッドをオーバーロードするだけで非常に簡単になり、技術的には柔軟性が高まります(ファイルから入力を読み取る機能を追加できます)。例えば)。

一般に、テストを容易にするために設計することを忘れないでください。また、リスクの高いパーツをリスクの低いパーツよりも厳しくテストしてください。つまり、リファクタリングは優れたツールであり、getInput(Scanner)をテストすることはgetInput()をテストすることよりもはるかに重要であり、特にnextLine()を呼び出すだけではありません。

スキャナーのモックを作成しないことを強くお勧めします。所有していない型をモックすることは悪い習慣であるだけでなく、スキャナーは、呼び出し順序が重要な相互に関連するメソッドの非常に大きなAPIを表します。 Mockitoでそれを複製するということは、Mockitoで大きな偽のスキャナー実装を作成するか、行う呼び出しのみをテストする最小限の実装をモックする(そして、変更が正しい結果を提供する場合でも、実装が変更されると中断する)ことを意味します。実際のスキャナーを使用して、外部サービス呼び出しや、定義していない小さな未作成のAPIをモックする場合のためにMockitoの練習を保存します。

まず最初に、テストの目的はユーザー入力がスキャナーから取得され、返された値がスキャナーに入力されたものであることを確認することであると想定します。

モックが機能しない理由は、getInput()メソッド内で毎回actualスキャナーオブジェクトを作成しているためです。したがって、mockitoインスタンスをどのように実行しても、呼び出されることはありません。したがって、このクラスをテスト可能にする正しい方法は、クラスのすべての外部依存関係を識別することです(この場合はJava.util.Scannerし、コンストラクタを介してそれらをクラスに注入します。この方法で、テスト中にモックスキャナーインスタンスを挿入できます。これは依存性注入に向けた基本的なステップであり、それが今度は良いTDDにつながります。例はあなたを助けるでしょう:

 package com.math.calculator;

    import Java.util.Scanner;

    public class InputOutput {

        private final Scanner scanner;

        public InputOutput()
        {
           //the external exposed default constructor 
           //would use constructor-chaining to pass an instance of Scanner.

           this(new Scanner(System.in));
        }

        //declare a package level constructor that would be visible only to the test class. 
      //It is a good practice to have a class and it's test within the same     package.
        InputOutput(Scanner scanner)
        {
            this.scanner  = scanner;
        }

        public String getInput() {

            return scanner.nextLine();
        }
    }

今あなたのテスト方法:

@Test
public void shouldTakeUserInput() {
    //create a mock scanner
    Scanner mockScanner = mock(Scanner.class);
    //set up the scanner
    when(mockScanner.nextLine()).thenReturn("add 5");

    InputOutput inputOutput= new InputOutput(mockScanner);

    //assert output
    assertEquals("add 5", inputOutput.getInput());

   //added bonus - you can verify that your scanner's nextline() method is
   //actually called See Mockito.verify
   verify(mockScanner).nextLine();
}

また、上記のクラスではコンストラクターを使用して注入しているため、スキャナーインスタンスをfinalと宣言していることに注意してください。このクラスには変更可能な状態がないため、このクラスはスレッドセーフです。

コンストラクタベースの依存性注入のコンセプトはかなりクールで、インターネットで読む価値があります。これは、スレッドセーフでテスト可能な優れたコードを開発する大きな方法を支援します。

0