2012年7月12日木曜日

標準出力をJUnitでテストするには

久しぶりにソースを公開してみます。

標準出力に結果を出すプログラムをJUnit 4.1でテストする方法とかで解説されていますが、
それを汎用的にしてみました。

名付けて、StandardOutputSnatcherクラスです。
略してSOS!

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

/**
 * 標準出力・標準エラー出力の出力先を奪うクラスです.
 * テストなどでコンソール出力の内容を知りたい場合に使います.
 * このクラスはスレッドセーフではありません。
 * マルチスレッド環境では利用しないでください。
 */
public final class StandardOutputSnatcher  {

    /** シングルトンパターンのためのインスタンス. */
    private static final StandardOutputSnatcher INSTANCE =
        new StandardOutputSnatcher();

    /** 元の標準出力. */
    private PrintStream nativeOut = null;

    /** 元の標準エラー出力. */
    private PrintStream nativeErr = null;

    /** 変更後の標準出力. */
    private ByteArrayOutputStream snatchedOut = new ByteArrayOutputStream();

    /** 変更後の標準エラー出力. */
    private ByteArrayOutputStream snatchedErr = new ByteArrayOutputStream();

    /** 奪取済みフラグ. */
    private boolean stealFlag = false;

    /** デフォルトコンストラクタの禁止. */
    private StandardOutputSnatcher() { }

    /**
     * このクラスのインスタンスを取得します.
     * このクラスはスレッドセーフ化しにくい形で標準出力に干渉するので、
     * シングルトンパターンを採用しました.
     * @return インスタンス
     */
    public static StandardOutputSnatcher getInstance() {
        return INSTANCE;
    }

    /**
     * 出力先を奪い、
     * コンソールからこのクラスへ出力するように設定します.
     */
    public void snatch() {
        // ダブルスティールの禁止
        if (!stealFlag) {
            nativeOut = System.out;
            nativeErr = System.err;
            System.setOut(new PrintStream(new BufferedOutputStream(snatchedOut)));
            System.setErr(new PrintStream(new BufferedOutputStream(snatchedErr)));
            stealFlag = true;
        }
    }

    /** 標準出力をクリアします. */
    public void clearOutput() {
        changedOut.reset();
    }

    /** 標準エラー出力をクリアします. */
    public void clearErrorOutput() {
        changedErr.reset();
    }

    /**
     * 標準出力の出力内容を取得します.
     * @return 標準出力の出力内容
     */
    public String getOutput() {
        System.out.flush();
        return snatchedOut.toString();
    }

    /**
     * 標準エラー出力の出力内容を取得します.
     * @return 標準エラー出力の出力内容
     */
    public String getErrorOutput() {
        System.err.flush();
        return snatchedErr.toString();
    }

    /**
     * 奪取した標準出力先を取得します.
     * @return 奪取した標準出力先
     */
    public PrintStream getNativeOutputStream() {
        return nativeOut;
    }

    /**
     * 奪取した標準エラー出力先を取得します.
     * @return 奪取した標準エラー出力先
     */
    public PrintStream getNativeErrorOutputStream() {
        return nativeErr;
    }

    /**
     * 出力先を元に戻します.
     * このメソッドの呼び忘れにご注意ください.
     */
    public void release() {
        // ダブルリリースの禁止
        if (stealFlag) {
            clearOutput();
            clearErrorOutput();
            System.setOut(nativeOut);
            System.setErr(nativeErr);
            stealFlag = false;
        }
    }

}

JUnitではないですが、
使い方はこんな感じです。

StandardOutputSnatcher sos = StandardOutputSnatcher.getInstance();
System.out.println("開始");
sos.snatch();

// 標準出力
System.out.print("標準出力テスト");
if (sos.getOutput().equals("標準出力テスト")) {
    sos.getNativeOutputStream().println("標準出力テスト成功!");
}

// 出力内容のクリア
sos.clearOutput();

// 標準出力(2回目)
System.out.print("標準出力テスト2");
if (sos.getOutput().equals("標準出力テスト2")) {
    sos.getNativeOutputStream().println("標準出力テスト2成功!");
}

// 標準エラー出力
io.clearErrorOutput();
System.err.print("標準エラー出力テスト");
if (sos.getErrorOutput().equals("標準エラー出力テスト")) {
    sos.getNativeOutputStream().println("標準エラー出力テスト成功!");
}

// 出力内容のクリア
sos.clearErrorOutput();

// 標準エラー出力(2回目)
System.err.print("標準エラー出力テスト2");
if (sos.getErrorOutput().equals("標準エラー出力テスト2")) {
    sos.getNativeOutputStream().println("標準エラー出力テスト2成功!");
}

sos.release();
System.out.println("終了");

ちなみにLog4jの出力テストにも使えることにお気づきでしょうか?
そうです、ConsoleAppenderで出力すれば、
Log4jで出力した内容をStringで取得できるのです。

適当にコピー&ペーストでお試しあれ。
…ちゃんと意味を理解したうえでね。

2012/07/08追記:微妙に修正

0 件のコメント:

コメントを投稿