読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

例外スローのテストをどう書くべきか

Java

例えば、

public class Sample {

    /**
     * do something.
     *
     * @param i
     *     a positive value.
     * @throws IllegalArgumentException
     *     if i is a negative value.
     */
    public static void doSomething(int i) {
        if (i < 0)
            throw new IllegalArgumentException("negative arg: " + i);
        
        ...
    }
}

というコードをテストする場合を考えます。

さて、『doSomethingはiが負数の場合にIllegalArgumentExceptionをスローします。』は
Javadocで明示的に宣言している公開API仕様ですから、それを満たすことを確かめる単体テストを行うべきです。


で、それをどう書くか。

JUnitアノテーションを使う

やり方だとこうなる。

@Test(expected = IllegalArgumentException.class)
public void testDoSomething() {

    Sample.doSomething(-1);
}

try-catchを使う

やり方だとこうなる。

@Test
public void testDoSomething() {

    try {
        Sample.doSomething(-1);
        fail();
    } catch (final IllegalArgumentException e) {
        assertTrue(e.getMessage().matches(".*nagative.+-1"));
    }
}

さて、一見したところ

アノテーションを使う方法のほうが、スッキリ書けてヨサゲな感じです。
だがしかし。


アノテーションを使う方法だと、『とにかくIllegalArgumentExceptionがスローされれば良い』
ということになるため、doSomethingの本体部分で何かの理由によりIllegalArgumentExceptionが
スローされてしまっても、テストには成功したように見えてしまいます。


例えば、こんな感じ。(かなり恣意的ですが)

class Sample {

    /** ... */
    static void doSomething(int i) {
        if (i < -1) // ←間違ってる
            throw new IllegalArgumentException("negative arg: " + i);

        new LinkedHashMap(i); // ←ココでIllegalArgumentException発生
    }
}


こんな風に間違っているコードに対して先ほどのテストを実行しても、
成功してしまうわけです。


というわけで、僕は常にtry-catchを使う方法を採用しています。


今日のテーマは大したことないように見えますが、
油断すると意外と差が付くこともあるよ、ということでした。はい。

ちなみに

org.junit.ExpectedExceptionなるクラスがあるそうです。
http://kentbeck.github.com/junit/javadoc/latest/org/junit/rules/ExpectedException.html

使い方はこんな感じ。

// These tests all pass.
public static class HasExpectedException {
    @Rule
    public ExpectedException thrown= new ExpectedException();

    @Test
    public void throwsNothing() {
// no exception expected, none thrown: passes.
    }

    @Test
    public void throwsNullPointerException() {
            thrown.expect(NullPointerException.class);
            throw new NullPointerException();
    }

    @Test
    public void throwsNullPointerExceptionWithMessage() {
            thrown.expect(NullPointerException.class);
            thrown.expectMessage("happened?");
            thrown.expectMessage(startsWith("What"));
            throw new NullPointerException("What happened?");
    }
}

。。。どうやら、EasyMockのように予め『例外がスローされるよ!』と宣言しておくスタイルのようです。
うーむ、なんかイマイチ分かり辛い。

というわけで、僕は使ってません。