標準入出力を横取りする

新人研修のプログラム課題においては、標準入出力を使うものが多々あります。
そういったものは目視で動作確認してるんですが、
やはり問題集を作るとなると、自動テストを回したい気がします。


import java.io.IOException;
import java.io.InputStream;

/**
 * 標準入力に対し、文字列入力を行う
 */
public class StandardInputSnatcher extends InputStream {

    private StringBuilder buffer = new StringBuilder();
    private static String crlf = System.getProperty("line.separator");

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

    @Override
    public int read() throws IOException {
        if (buffer.length() == 0) {
            return -1;
        }
        int result = buffer.charAt(0);
        buffer.deleteCharAt(0);
        return result;
    }
}
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.StringReader;

/**
 * 標準出力に対し、読み出しができるようにする
 */
public class StandardOutputSnatcher extends PrintStream {
    private BufferedReader buffer = new BufferedReader(new StringReader(""));

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

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

テスト対象コード

package target;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * 標準入出力を使用しまくりのサンプルコード
 */
public class StandardIOSample {

    public static void main(String[] args) {
        try {
            BufferedReader stdReader = new BufferedReader(new InputStreamReader(System.in));
            for (int i = 0; i < 2; i++) {
                System.out.println("INPUT: ");
                String line;
                line = stdReader.readLine();
                System.out.println("OUTPUT: " + line);
            }
            stdReader.close();
            System.out.println("PROGRAM END");
        } catch (Exception e) {
            e.getStackTrace();
            System.exit(-1); // プログラムを終了
        }
    }
}

やりたかったこと。

package target;

import org.hamcrest.core.Is;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import StandardInputSnatcher;
import StandardOutputSnatcher;

/**
 * 標準入出力の結果自体をテストすることができる。
 */
public class TestStandardIOSample {
    private StandardOutputSnatcher out = new StandardOutputSnatcher();
    private StandardInputSnatcher in = new StandardInputSnatcher();

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

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

    @Test
    public void testHello() {
        in.inputln("aaa"); // 標準入力への入力はあらかじめ全部与える。
        in.inputln("bbb");
        StandardIOSample.main(null); // テストターゲット実行
        Assert.assertThat(out.readLine(), Is.is("INPUT: ")); // 出力は改行ごとに取得される
        Assert.assertThat(out.readLine(), Is.is("OUTPUT: aaa"));
        Assert.assertThat(out.readLine(), Is.is("INPUT: "));
        Assert.assertThat(out.readLine(), Is.is("OUTPUT: bbb"));
        Assert.assertThat(out.readLine(), Is.is("PROGRAM END"));
    }
}

うーむ、どうかなぁ。


参考資料。ありがとうございました。
http://d.hatena.ne.jp/shawshank99/20110917/1316242496