【Java】標準入力＆標準出力をJUnitでテストしたい

# きっかけ
最近競技プログラミングの勉強をはじめた。ローカルで動作確認をする際にいちいち入力するのがめんどくさい。JUnitでやりたい。

# 環境
- Windows10
- IntelliJ IDEA Community Edition 2019.1.1
- Java 1.8.0_211

# 参考にした記事
[Qiita - JUnitで標準入力をつっこんで流す](https://qiita.com/Takmiy/items/ea234d2211aba66e7659)
[kencobaの日記 - 標準入出力を横取りする](https://kencoba.hatenadiary.org/entry/20120831/1346398388)
[にょきにょきブログ - System.out を置き換えよう](http://aoking.hatenablog.jp/entry/20110917/1316242496)

# 概要
`System`クラスの`setOut`、`setIn`で出力先と入力先を標準出力、入力から変えることができる。
Outには`PrintStream`、Inには`InputStream`を継承したクラスを定義して設定すればよい。

# 実装
入力クラスと出力クラスを作成する。

### 入力クラス - StandardInputStream

```java:StandardInputStream.java
import java.io.InputStream;

public class StandardInputStream extends InputStream {
    private StringBuilder sb = new StringBuilder();
    private String lf = System.getProperty("line.separator");

    /**
     * 文字列を入力する。改行は自動的に行う
     * @param str 入力文字列
     */
    public void inputln(String str){
        sb.append(str).append(lf);
    }

    @Override
    public int read() {
        if (sb.length() == 0) return -1;
        int result = sb.charAt(0);
        sb.deleteCharAt(0);
        return result;
    }
}
```

### 出力クラス - StandardOutputStream

```java:StandardOutputStream.java
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.StringReader;

public class StandardOutputStream extends PrintStream {
    private BufferedReader br = new BufferedReader(new StringReader(""));

    public StandardOutputStream() {
        super(new ByteArrayOutputStream());
    }

    /**
     * 1行分の文字列を読み込む
     * @return 改行を含まない文字。終端の場合はnull
     */
    public String readLine() {
        String line = "";
        try {
            if ((line = br.readLine()) != null) return line;
            br = new BufferedReader(new StringReader(out.toString()));
            ((ByteArrayOutputStream) out).reset();
            return br.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
```

### テスト対象コード
テスト対象コードはatcoderさんから拝借。
[PracticeA - Welcome to AtCoder](https://atcoder.jp/contests/abs/tasks/practice_1)

>**問題**
>整数 a,b,cと、文字列 s が与えられます。 a+b+c の計算結果と、文字列 s を並べて表示しなさい。

>**入力**

>```
a
b c
s
```


```java:WelcomeToAtCoder.java
import java.util.Scanner;

public class WelcomeToAtCoder {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner(System.in)) {
            int r = sc.nextInt();
            r += sc.nextInt();
            r += sc.nextInt();
            String s = sc.next();
            System.out.println(r + " " + s);
        }
    }
}
```

### テストコード

```java:WelcomeToAtCoderTest.java
import WelcomeToAtCoder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import util.io.StandardInputStream;
import util.io.StandardOutputStream;

import static org.hamcrest.CoreMatchers.is;

public class WelcomeToAtCoderTest {

    private StandardInputStream in = new StandardInputStream();
    private StandardOutputStream out = new StandardOutputStream();

    @Before
    public void before() {
        System.setIn(in);
        System.setOut(out);
    }

    @After
    public void after() {
        System.setIn(null);
        System.setOut(null);
    }

    @Test
    public void main01() {
        in.inputln("1");
        in.inputln("2 3");
        in.inputln("hoge");
        WelcomeToAtCoder.main(null);
        Assert.assertThat(out.readLine(), is("6 hoge"));
    }
}
```

事前処理で入出力を切り替えてテストする。

# おわりに
テストケースを書くのは簡単なので実装が楽になった気がする。
これに限らずテストはちゃんと書こうと思った。
TDD勉強したいなあ。
~~JUnit久しぶりにさわった~~