JUnitの使い方(初級)


JUnitの使い方についてです。
職場でテストコードを普及させるために書いているので、JUnitは古めのバージョンです。

はじめに

テストを書く習慣を導入したい理由

  • リファクタリングを気軽にできる環境にしたい
  • デグレーションを検知したい
  • 開発スピード(テストスピード含む)を上げたい
  • レビューアの負担を下げたい

テストコードを書き始めてすぐに効果が出るとは思っていませんが、ちょっとずつ改善できればと思っています。
テストコードを書くと工数が・・・ とか スピードが・・・って話が出てくると思うのですが、時間がかかるのは慣れるまでの話で、実際書き始めれば手戻りも減るし自分のコードに対してテストするので品質も上がりやすいし、全体として良いことの方が多いと思います。

テストコードの有益性については、色々な記事が出ているのでそちらを見ていただければ。

参考

テストがなかった無法地帯にテストを導入して開発速度を1.7倍にした話
技術的負債とどうやって戦うか

じゃあどこまでを目標にするか

このあたりは色々と話し合いながら詰めていきたいのですが、個人的にはガバレッジを90%以上目指すとかは今のところはなく、ひとまずは習慣づけていって、今後のフレームワーク移行とかに備えられればと思っています。

近い将来でやりたいことはこんな感じです。

  • CIで自動テスト
    テストコードを書いて終わりではなく、見直していきたい(デグレ防止)

  • ガバレッジ率の見える化
    チームの頑張りを見える化して、モチベーションをキープしたい

  • 開発体制(レビューの目的とか)の見直し
     テストコードを書いていけば、自然とコードレビューの目的とかも変わってくると思うので、見直していきたい。また、QAチームとの役割分担なども必要になるかも。

実行環境

  • Java 1.8
  • JUnit 4.4

簡単なテストのサンプル

まずはざっくりとテストについて理解してもらうために、簡単なテストの例を記載します。

プロダクションコード
public class HelloJunit {

    public String sayHello() {
        return "Hello";
    }
}
テストコード
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class HelloJunitTest {

    @Test
    public void Helloと言う() {
        HelloJunit helloJunit = new HelloJunit();
        assertThat(helloJunit.sayHello(), is("Hello"));
    }
}

テストコード解説

クラス名

テストクラス名はテスト対象プロダクションコードのクラス名にTestを付与した名前を設定します。
これによって、テスト対象クラスとテストコードがテスティングペアとして認識されます。

テストメソッド

テストを行うためにメソッドを作成します。
テストはなるべく1ケースにつき1メソッドを作成してください。
(複数のテストを同一メソッド内で作成しない)
また、メソッド名はテストケースの内容がわかりやすいように日本語で設定します。

メソッドに対して @ Test アノテーションを付与することでテスト実行時にテスト対象メソッドとして実行されます。

assertThat / is

値を比較する際には、assertThatを利用します。
assertThatの使い方は、assertThat(期待値, Matcherメソッド(実測値));となります。

isメソッドは対象オブジェクトのequalsを利用して比較を行います。
今回は"Hello"文字列同士が比較され trueとなるため、テストが成功します。

実行結果

右側のグラフがグリーンになっていれば成功です。
詳細は左側の 実行、エラー、失敗 を確認。

失敗した場合

テストに失敗した場合には、ログ上にエラーの内容が出力されます。(右側のグラフがレッドになります)
障害トレースに具体的な比較結果(値)が表示されますので、これを参照しながら修正していく必要があります。

アノテーション

Test

JUnitがテストメソッドとして認識するためのアノテーションです。
このアノテーションがついていればテスト対象となります。
メソッドはpublic voidになっている必要があります。

Testアノテーション
@Test
public void Helloと言う() {
    helloJunit = new HelloJunit();
    assertThat(helloJunit.sayHello(), is("Hello"));
}

Before

このアノテーションがついているメソッドは、テストを実行するより前に実行されます。
何か共通系の処理などを行うために利用されます。

以下の例では、共通で利用するテスト対象クラスをインスタンス化する処理を入れています。

Beforeアノテーション
@Before
public void setUp() {
    helloJunit = new HelloJunit();
}

After

このアノテーションがついているメソッドは、すべてのテストが実施された後最後に実行されます。
Beforeで外部リソースを割り当てて、Afterで解放するといった利用方法があります。

Afterアノテーション
@Before
public void setUp() {
    helloJunit = new HelloJunit();
    File file = new File(FILE_PATH);
    try {
        filereader = new FileReader(file);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}

@After
public void termDown() {
    try {
        filereader.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Ignore

このアノテーションがついているメソッドはTestアノテーションがついていてもSkipされます。
何かしらの都合により、一時的にテストしたくない場合などに利用します。

@Ignore
@Test
public void Helloと言う() {
    helloJunit = new HelloJunit();
    assertThat(helloJunit.sayHello(), is("Hello"));
}

Matcher API

is

equalsメソッドを利用して比較されます。
プリミティブ型をセットした場合は、ラッパークラスに変更されてequalsで比較されます。

is
@Test
public void isを試す() {
    assertThat("Hello", is("Hello"));
    assertThat(1, is(1));
}

not

notは、比較した結果がfalseであることを確かめるテストになります。
assertThat("Hello", not("bye")); という使い方もできますが、英文的に正しくなるようにis(not(~))を使う方が好ましいです。

not
@Test
public void notを試す() {
    assertThat("Hello", is(not("bye")));
}

null value

そのままNullであることを確かめます。
逆に、notNullValueもあります。

nullValue
@Test
public void nullValueを試す() {
    assertThat(null, nullValue());
}

instanceOf(isA)

型が同一かを確かめます。
補足:新しいバージョンのJUnitではisAというもので代替できます。

instanceOf
@Test
public void instanceOfを試す() {
    assertThat("Hello", instanceOf(String.class));
}

sameInstance

同一インスタンスか確かめます。

sameInstance
@Test
public void sameInstanceを試す() {
    assertThat("Hello", sameInstance("Hello"));
}

allOf

allOf内のMatcherが全てtrueであれば成功となります。

allOf
@Test
public void allOfを試す() {
    assertThat("Hello", allOf(not("bye"),sameInstance("Hello")));
}

その他

JUnitのバージョンがもう少し上であれば、hasItemhasItemsとかstartsWithとかの便利なやつがあるんですが、、、
詳しく知りたい方はこちらをご参照ください。

例外をチェックする

例外が出力されることをテストする方法になります。
テストの方法は2つあります。

  1. Testアノテーション内にtargetを設定
  2. try catchしてチェック

以下はプロダクションコードのサンプルです。

プロダクションコード
public class HelloJunit {
    public void throwException() {
        throw new IllegalArgumentException("引数が不正です");
    }
}

Testアノテーションでチェック

Testアノテーションにexpectedを利用してExceptionが発生するクラスを指定することでテスト可能です。

Testアノテーションで確かめる
@Test(expected = IllegalArgumentException.class)
public void ExceptionをTestアノテーションで検査() {
    helloJunit.throwException();
}

catchして確認する

テスト対象コードをtry catchすることで、比較することが可能です。
こちら方法の場合は、Exceptionされた際のメッセージを検証することが可能です。

補足:新しいバージョンのJUnitではRuleアノテーションというもので代替できます。

catchして確かめる
@Test
public void Exceptionをcatchで検査() {
    try {
        helloJunit.throwException();
    } catch (IllegalArgumentException expected) {
        assertThat(expected.getMessage(), is("引数が不正です"));
    }
}

参考資料

その他、良さそうなQiita記事
HamcrestのMatchersに定義されているメソッドの使い方メモ