GitHub + Travis CI + CoverallsでJavaプログラムのカバレッジ計測をする方法

Travis CICoverallsを使って、GitHubに公開しているJavaプログラムのカバレッジを測定する方法を以下に記載する(Mavenで管理されたJavaプロジェクトを想定)。

Travis CIとCoverallsの連携にはCoverallsの公式ページ(以下)で紹介されているcoveralls-maven-pluginを利用した。


ステップ1(事前準備)
GitHubTravis CIの連携は以下のサイト等を参考に実施しておく。

また、Coverallsのサイトにログインして、カバレッジ計測対象のGitHubリポジトリを有効化しておく。


ステップ2(pom.xmlの編集)
Javaプロジェクトのpom.xmlに以下を追加する。

<plugin>
    <groupId>org.eluder.coveralls</groupId>
    <artifactId>coveralls-maven-plugin</artifactId>
    <version>2.2.0</version>
</plugin>

GitHubのcoveralls-maven-pluginのREADMEを見ると、CoverallsのRepo Tokenも書いているが、GitHubの公開リポジトリを利用している場合には、記載してはダメのようだ。

次にpom.xmlに以下のような記載を追加することで、利用するカバレッジ測定ツールを指定する(以下はCoberturaの例)。JaCoCo等を使う場合にはcoveralls-maven-pluginのREAMMEを参照。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>cobertura-maven-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <format>xml</format>
        <maxmem>256m</maxmem>
        <!-- aggregated reports for multi-module projects -->
        <aggregate>true</aggregate>
    </configuration>
</plugin>


ステップ3(.travis.ymlの編集)
GitHubTravis CI連携用に.travis.ymlファイルを作成していると思うが、その.travis.ymlに以下を追加する。

after_success:
  - mvn clean cobertura:cobertura coveralls:cobertura


以上で準備は完了。
GitHubソースコードをpushすると、自動的にTravis CIでビルドとテストが走り、その後テストデータがCoverallsに自動転送され、テストの統計データを見られるようになる。
CoverallsのプロジェクトページのTECHNICAL DETAILSにバッジが表示されているが、以下のようにカバレッジ率が表示されていれば(unknownじゃなくなっていれば)連携成功である。

ドットインストールのレッスン「ローカル開発環境の構築」の補足

「ローカル開発環境の構築」というレッスンで「#04 Webサーバーを導入しよう」というのがある。
ここでは、Webサーバをインストールした後に、Webブラウザでアクセスしてみて正しくインストール出来たかを確認しているのだが、レッスン通りにやっても上手くウェブページが表示出来なかった場合には、Linuxファイヤーウォールiptables)を停止してみよう。
具体的には以下のコマンドを実行する。

$ sudo service iptables stop


これでウェブページが表示出来るようになると思う。
あとは再起動後にiptablesが起動しないように、以下のコマンドを実行しておく(あくまでレッスン用の環境の場合です)。

$ sudo chkconfig iptables off

MockitoのArgumentMatcherの使い方

http://docs.mockito.googlecode.com/hg/latest/org/mockito/ArgumentMatcher.htmlを見ると、このクラスを使って以下のように、IsListOfTwoElementsクラスのような独自のArgument Matcherを作る事が出来る。

class IsListOfTwoElements extends ArgumentMatcher<List> {
    public boolean matches(Object list) {
        return ((List) list).size() == 2;
    }
}

List mock = mock(List.class);
when(mock.addAll(argThat(new IsListOfTwoElements()))).thenReturn(true);
mock.addAll(Arrays.asList("one", "two"));
verify(mock).addAll(argThat(new IsListOfTwoElements()));


また、argThat(new IsListOfTwoElements())の所は、以下のようにメソッドにすると、可読性が高くなると書かれている。

verify(mock).addAll(argThat(new IsListOfTwoElements()));
//becomes
verify(mock).addAll(listOfTwoElements());


メソッドにするやり方は書かれていないが、以下のようにすれば良い。

class IsListOfTwoElements extends ArgumentMatcher<List> {
    public boolean matches(Object list) {
        return ((List) list).size() == 2;
    }
    public static List listOfTwoElements() {
        return argThat(new IsListOfTwoElements());
    }
}


ちなみにジェネリックを使うと以下のような感じか。

class IsListOfTwoElements extends ArgumentMatcher<List<String>> {
    @Override
    public boolean matches(Object list) {
        return ((List<?>) list).size() == 2;
    }	
    public static List<String> listOfTwoElements() {
        return argThat(new IsListOfTwoElements());
    }
}

@SuppressWarnings("unchecked")
List<String> mock = mock(List.class);
when(mock.addAll(argThat(new IsListOfTwoElements()))).thenReturn(true);
mock.addAll(Arrays.asList("one", "two"));
verify(mock).addAll(listOfTwoElements());

System.out.printとかSystem.exitとかのユニットテストを補助してくれるルールを試してみる

System.out.printlnとかSystem.exitをテストするのはちょっと面倒なのだが、その辺りを補助してくれるJUnitのルールのコレクション(System Rules)があったので試してみた。


【事前準備】
System Rulesを使うにはApache Commons IOが必要という事でダウンロードしておく。


ユニットテストの例】
テスト対象クラスは以下のように標準出力や標準エラー出力に文字列を出力するメソッドやSystem.exit(0)を呼び出すメソッドがある。

public class MyClass {
    public void out() {
        System.out.print("Hoge");
    }	
    public void err() {
        System.err.print("Moge");
    }	
    public void exit() {
        System.exit(0);
    }	
}


上記のテスト対象クラスをSystem Rulesを使ってテストするコードは以下のようになる。かなりシンプルにテストコードが書けた。

public class MyClassTest {	
    @Rule
    public final StandardOutputStreamLog stdout = new StandardOutputStreamLog();	
    @Rule
    public final StandardErrorStreamLog stderr = new StandardErrorStreamLog();	
    @Rule
    public final ExpectedSystemExit exit = ExpectedSystemExit.none();	

    MyClass sut;
    @Before
    public void setUp() throws Exception {
        sut = new MyClass();
    }
    @Test
    public void testOut() {
        sut.out();
        assertThat(stdout.getLog(), is("Hoge"));
    }
    @Test
    public void testErr() {
        sut.err();
        assertThat(stderr.getLog(), is("Moge"));
    }	
    @Test
    public void testExit() {
        exit.expectSystemExitWithStatus(0);
        sut.exit();
    }	
}


System Rulesは上記以外にも、システムプロパティのテストを補助するProvideSystemPropertyやセキュリティーマネージャのテストを補助するProvideSecurityManagerといったルールがあるようだ。


【関連記事】

MockitoのMatchers.anyObject()の使い方

MockitoのMatchers.anyObject()は以下のmethodA()のように、あるオブジェクトを引数に持つメソッドが呼び出されたかどうかを、Mockitoで検証する際等に使うことが出来る。

public class MyClass {
    
    private MyField field;
    	
    public void setField(MyField field) {
        this.field = field;
    }
    
    public void methodA(String text) {
        field.methodB(new MyObject(text));
    }
}


具体的な検証コードは以下。

public class MyClassTest {

    MyField mock;
    MyClass sut;

    @Before
    public void setUp() throws Exception {
        mock = mock(MyField.class);
        sut = new MyClass();
        sut.setField(mock);
    }

    @Test
    public void testMethodA() {
        sut.methodA("test");
        // methodA()を呼び出すとその先でMyFieldのmethodB()が呼び出される事を検証
        verify(mock).methodB((MyObject)anyObject());
    }

}


なお、検証コードを以下のようにしても良さそうなのだが、実際にmethodB()が呼び出された際に渡されたオブジェクトと同一のインスタンスでは無いため、以下はエラーとなってしまう。このような場合にMatchers.anyObject()が使える。

verify(mock).methodB(new MyObject("test"));

Mockitoでprivateなフィールドをモック化する方法

以下のようにprivateなフィールドをモック化する場合、リフレクションを使うと出来るのだが、Mockitoにはそのリフレクションを簡単に使えるWhiteboxというユーティリティクラスがある。

public class MyClass {

    private final MyField field = new MyField();

    public void methodA() {
        field.methodB();
    }

}

Whiteboxクラスの使い方は以下(MyFieldクラスをモック化し、メソッドが呼ばれている事を検証する例)。

public class MyClassTest {

    MyClass sut;
	
    @Before
    public void setUp() throws Exception {
        sut = new MyClass();
        Whitebox.setInternalState(sut, "field", mock(MyField.class));
    }

    @Test
    public void testMethodA() {
        sut.methodA();
        verify((MyField)Whitebox.getInternalState(sut, "field")).methodB();
    }

}

ドットインストールのExpress入門の補足

ドットインストールの「Express入門」「#19 記事を更新/削除してみよう」でちょっと躓いたのでメモしておく。

具体的には記事の削除ボタンを押すと、一番上の記事しか消えないという状況に。
で、理由を調べてみるとpost.jsの以下の記事削除処理で、req.body.idがundefinedになっている事が原因と分かった(splice()の第一引数にundefinedを渡すと0と同じになる?)。

exports.destroy = function(req, res) {
    posts.splice(req.body.id, 1);
    res.redirect('/');
};

で、何でundefinedになってしまうかと言うと、index.ejsで以下のようにidをhidden属性で渡していないから。

<form method="post" action="/posts/<%= i %>">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="del">
</form>

ということで、以下のようにすれば問題解決。

<form method="post" action="/posts/<%= i %>">
<input type="hidden" name="id" value="<%= i %>">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="del">
</form>