Java中測試異常的多種方式

黃博文發表於2014-04-13

使用JUnit來測試Java程式碼中的異常有很多種方式,你知道幾種?

給定這樣一個class。

Person.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Person {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {

        if (age < 0 ) {
            throw new IllegalArgumentException("age is invalid");
        }
        this.age = age;
    }
}

我們來測試setAge方法。

Try-catch 方式

1
2
3
4
5
6
7
8
9
10
11
    @Test
    public void shouldGetExceptionWhenAgeLessThan0() {
        Person person = new Person();
        try {
        person.setAge(-1);
            fail("should get IllegalArgumentException");
        } catch (IllegalArgumentException ex) {
            assertThat(ex.getMessage(),containsString("age is invalid"));
        }

    }

這是最容易想到的一種方式,但是太囉嗦。

JUnit annotation方式

JUnit中提供了一個expected的annotation來檢查異常。

1
2
3
4
5
6
    @Test(expected = IllegalArgumentException.class)
    public void shouldGetExceptionWhenAgeLessThan0() {
        Person person = new Person();
        person.setAge(-1);

    }

這種方式看起來要簡潔多了,但是無法檢查異常中的訊息。

ExpectedException rule

JUnit7以後提供了一個叫做ExpectedException的Rule來實現對異常的測試。

1
2
3
4
5
6
7
8
9
10
11
12
    @Rule
    public ExpectedException exception = ExpectedException.none();

    @Test
    public void shouldGetExceptionWhenAgeLessThan0() {

        Person person = new Person();
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage(containsString("age is invalid"));
        person.setAge(-1);

    }

這種方式既可以檢查異常型別,也可以驗證異常中的訊息。

使用catch-exception庫

有個catch-exception庫也可以實現對異常的測試。

首先引用該庫。

pom.xml
1
2
3
4
5
6
        <dependency>
            <groupId>com.googlecode.catch-exception</groupId>
            <artifactId>catch-exception</artifactId>
            <version>1.2.0</version>
            <scope>test</scope> <!-- test scope to use it only in tests -->
        </dependency>

然後這樣書寫測試。

1
2
3
4
5
6
7
8
    @Test
    public void shouldGetExceptionWhenAgeLessThan0() {
        Person person = new Person();
        catchException(person).setAge(-1);
        assertThat(caughtException(),instanceOf(IllegalArgumentException.class));
        assertThat(caughtException().getMessage(), containsString("age is invalid"));

    }

這樣的好處是可以精準的驗證異常是被測方法丟擲來的,而不是其它方法丟擲來的。

catch-exception庫還提供了多種API來進行測試。

先載入fest-assertion庫。

1
2
3
4
5
        <dependency>
            <groupId>org.easytesting</groupId>
            <artifactId>fest-assert-core</artifactId>
            <version>2.0M10</version>
        </dependency>

然後可以書寫BDD風格的測試。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    @Test
    public void shouldGetExceptionWhenAgeLessThan0() {
        // given
        Person person = new Person();

        // when
        when(person).setAge(-1);

        // then
        then(caughtException())
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessage("age is invalid")
                .hasNoCause();
    }

如果喜歡Hamcrest風格的驗證風格的話,catch-exception也提供了相應的Matcher API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    @Test
    public void shouldGetExceptionWhenAgeLessThan0() {
        // given
        Person person = new Person();

        // when
        when(person).setAge(-1);

        // then
        assertThat(caughtException(), allOf(
                instanceOf(IllegalArgumentException.class)
                , hasMessage("age is invalid")
                ,hasNoCause()));
    }

第一種最土鱉,第二種最簡潔,第四種最靠譜。

相關文章