おしりんブログ

新人PGおしりんの色々まとめるよブログ

Power系のMock伊藤さんの話

こんばんよ~


すっかりさぼってました。
食あたりにやられ、夏の暑さにやられ、EOF状態でした。(?)


最近は正規表現の鬼こと、もちわきとログ監視ツールを作っています。

実装はお任せということで、ガリガリ書いています。
ガリガリ君ばりにガリガリです。(盛った)


今日は単体テストの救世主、PowerMockについてまとめたいと思うよ。

(そろそろもう少しかっこよくブログをまとめたい)
(マークダウン使ってみたい)
(mockについてはhttp://shioriwest.hatenablog.com/entry/2014/06/23/000616にまとめてあるよ)


・PowerMockとは
 本来Javaの制限でテストをすることが困難である
 staticメソッドや、privateメソッドコンストラクタなどを
 モックとして簡単に実装出来るようにするライブラリである。


早速だけど、サンプルコードたちを投下。

テストしたいメソッド

public class Sample {
  private static void selectByLog(String path, String searchWord)
    throws IOException {
    
    BufferedReader br = Utils.readText(path);
    String str;
    int count = 1; //ファイルの行数
  
    System.out.println("検索内容[ファイル名=" + path + "]");
  
    while ((str = br.readLine()) != null) {
      if (str.indexOf(searchWord) != -1) {
        System.out.print("[" + count + "]");
        System.out.println(str);
      }
      count++;
    }
    
    System.out.println("出力完了。");
    br.close();
  }
}


メソッドの引数に解析したいログファイルのパスとログの中から検索したい文字列を渡す
→ログファイルの中から検索したい文字列を含む行があるか探す
→検索したい文字列を含む行があったら、行数と検索したい文字列が含まれている1行を出力する

って感じのメソッド


このメソッドの中でStaticなメソッドを呼んでいる部分はこの部分。

BufferedReader br = Utils.readText(path);

readTextメソッドの中身はこんな感じ。
BufferedReaderをnewしているだけ(こんなメソッドあるのかな…)

public class Utils {
  static public BufferedReader readText(String path)
  throws IOException {

    BufferedReader br = null;
    br = new BufferedReader(new InputStreamReader
    (new FileInputStream(path), "UTF-8"));

    return br;
  }
}


テストをする時は、メソッドがちゃんと動いているかを確認すればいいので、
実際にファイルを読み込ませる必要は特にない。

ので、BufferedReaderをmockにして、readLineメソッドが呼ばれた時に、
私の指定した一行か二行かを返してくれればいいんでないか?

ということでこんなテストを作ってみた。
(いつもにも増して日本語がまとまらない)

@RunWith(PowerMockRunner.class)
@PrepareForTest(Utils.class)
public static class selectByLogTest {

  private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();

  @Before
  public void setUpStreams() {
    System.setOut(new PrintStream(outContent));
  }

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

  @Test
  public void 検索したい文字列がログに含まれている場合() throws IOException {
  
    BufferedReader br = mock(BufferedReader.class);

    mockStatic(Utils.class);
    when(Utils.readText((String) any())).thenReturn(br);

    when(br.readLine())
        .thenReturn(
            "8月21日におしりんが20日ぶりにブログを更新しました。")
        .thenReturn(null);

    Sample.selectBylog("おしりんブログログ", "おしりん");

    String expected = "検索内容[ファイル名=おしりんブログログ]"
        + "\r\n"
        + "[1]8月21日におしりんが20日ぶりにブログを更新しました。"
        + "\r\n" + "出力を完了しました。" + "\r\n";

    assertThat(outContent.toString(), is(expected));
  }
}


まず重要なのがここ

@RunWith(PowerMockRunner.class)
@PrepareForTest(Utils.class)

PowerMock使うぞ宣言と、どのクラスをPowerMock化するのか宣言。


で、実際にPowerMock化しているところがここ。

mockStatic(Utils.class);
when(Utils.readText((String) any())).thenReturn(br);

なぜかanyString()を使うとエラーが出る…ので、
(String) any()で強行しました。

MockとPowerMockの宝石箱や~(?)


ちなみに@Beforeやら@Afterでごちゃごちゃやってるのは
Systemクラスのoutの出力先を
テストクラスの1行目でnewしているByteArrayOutputStreamに挿げ替えてる。のだ。

コンソールに出力されているかをテストしたい時はこうするとイイ。


あとちなみに何でbr.readLine()のthenReturn()を二回書いているのかというと、

一回目にbr.readLine()が呼ばれた時は一つ目のthenReturnで指定したものを返す。
二回目にbr.readLine()が呼ばれた時は二つ目のthenReturnで指定したものを返す。

っていう風に、
br.readLine()が呼ばれた時のアクションをそれぞれ一回目と二回目で別物を指定しているから。
(やっぱり上手くまとまらない)

mock化したインスタンスはthenReturnとかthenThrow(確か)とか
色々メソッドのアクションを指定することが出来る~

べんり~


あ、やばい眠い。おやすミンゴス