JUnit4學習筆記(三):assertThat語法與Matcher

一、使用JUnit的一般測試語法

org.junit.Assert類裏有各種斷言方法,大部分情況下我們會像下面這個例子一樣編寫測試:

public class AssertThatTest {
    private int id = 6;
    private boolean trueValue = true;
    private Object nullObject = null;
    private String msg = "Hello World";

    @Test
    public void testAssert() throws Exception {
        assertEquals(6, id);
        assertTrue(trueValue);
        assertNull(nullObject);
        assertTrue(msg != null && msg.startsWith("Hello") && msg.endsWith("World"));
    }
}

 但是這些基本的斷言有些可讀性並不是很好,例如上面最後一個斷言,判斷一個字符串以“Hello”開頭,以“Workd”結尾,由於沒有assertStartWith和assertEndWith之類的函數,我們不得不自己編寫表達式並斷言其結果。並且因爲我們沒有提供失敗的信息,當這個斷言失敗時只會拋出java.lang.AssertionError,根本不知道是因爲msg爲null還是msg的內容錯誤。

 

二、使用assertThat與Matcher

在org.junit.Assert中除了常用的相等、布爾、非空等斷言,還有一種assertThat,需要配合org.hamcrest.Matcher使用,這種斷言的語法爲:

assertThat([reason, ]T actual, Matcher<? super T> matcher),其中,reason爲斷言失敗時的輸出信息,actual爲斷言的值或對象,matcher爲斷言的匹配器,裏面的邏輯決定了給定的actual對象滿不滿足斷言。

在org.hamcrest.CoreMatchers類中組織了所有JUnit內置的Matcher,調用其任意一個方法都會創建一個與方法名字相關的Matcher。

使用assertThat重寫上述方法:

public class AssertThatTest {
    private int id = 6;
    private boolean trueValue = true;
    private Object nullObject = null;
    private String msg = "Hello World!";

    @Test
    public void testAssertThat() throws Exception {
        //由於靜態導入了org.haibin369.matcher.MyMatchers.*,可以調用裏面的
        //is(), nullValue(), containsString(), startsWith()方法,可讀性更好
        assertThat(id, is(6));
        assertThat(trueValue, is(true));
        assertThat(nullObject, nullValue());
        assertThat(msg, both(startsWith("Hello")).and(endsWith("World")));
    }
}

 重寫後的測試和之前的效果一模一樣,但是可讀性更好了,最後一個斷言,能一眼看出來是要以“Hello”開頭並以“World”結尾的字符串。如果把startsWith("Hello")改成startsWith("Helloo"),它的失敗信息也比較直觀:

java.lang.AssertionError: 
Expected: (a string starting with "Helloo" and a string ending with "World")
     but: a string starting with "Helloo" was "Hello World!"
 

三、自定義Matcher

現在我們有一個User對象,只包含兩個變量機器setter和getter:username,password,當username和password都爲“admin”時表示是管理員(Admin User)。現在我們來創建一個自己的Matcher並運用到assertThat語法中去。

 首先看看org.hamcrest.Matcher接口的源碼

/**
 * A matcher over acceptable values.
 * A matcher is able to describe itself to give feedback when it fails.
 * <p/>
 * Matcher implementations should <b>NOT directly implement this interface</b>.
 * Instead, <b>extend</b> the {@link BaseMatcher} abstract class,
 * which will ensure that the Matcher API can grow to support
 * new features and remain compatible with all Matcher implementations.
 * <p/>
 * For easy access to common Matcher implementations, use the static factory
 * methods in {@link CoreMatchers}.
 * <p/>
 * N.B. Well designed matchers should be immutable.
 * 
 * @see CoreMatchers
 * @see BaseMatcher
 */
public interface Matcher<T> extends SelfDescribing {

    boolean matches(Object item);
    
    void describeMismatch(Object item, Description mismatchDescription);

    @Deprecated
    void _dont_implement_Matcher___instead_extend_BaseMatcher_();
}

 類註釋上強調,Matcher實現類不應該直接實現這個接口,而應該繼承org.hamcrest.BaseMatcher抽象類

public abstract class BaseMatcher<T> implements Matcher<T> {

    /**
     * @see Matcher#_dont_implement_Matcher___instead_extend_BaseMatcher_()
     */
    @Override
    @Deprecated
    public final void _dont_implement_Matcher___instead_extend_BaseMatcher_() {
        // See Matcher interface for an explanation of this method.
    }

    @Override
    public void describeMismatch(Object item, Description description) {
        description.appendText("was ").appendValue(item);
    }

    @Override
    public String toString() {
        return StringDescription.toString(this);
    }
}

 編寫IsAdminMatcher,需要實現兩個方法,第一個是matches,判斷給定的對象是否是所期待的值,第二個是describeTo,把應該得到的對象的描述添加進Description對象中。 

/**
 * 斷言一個給定的User對象是管理員
 */
public class IsAdminMatcher extends BaseMatcher<User> {
    /**
     * 對給定的對象進行斷言判定,返回true則斷言成功,否則斷言失敗
     */
    @Override
    public boolean matches(Object item) {
        if (item == null) {
            return false;
        }

        User user = (User) item;
        return "admin".equals(user.getUsername()) && "admin".equals(user.getPassword());
    }

    /**
     * 給期待斷言成功的對象增加描述
     */
    @Override
    public void describeTo(Description description) {
        description.appendText("Administrator with 'admin' as username and password");
    }
}

 

執行測試:

public class AssertThatTest {
    User user = new User("haibin369", "123456");

    @Test
    public void testAdmin() throws Exception {
        assertThat(user, new IsAdminMatcher());
    }
}

 測試可以正常執行,但是上面的User對象並不是管理員,因此測試會失敗,以下信息會輸出:

java.lang.AssertionError: 
Expected: Administrator with 'admin' as username and password
     but: was <org.haibin369.model.User@570b13e4>

 查看源代碼,我們發現but後面的信息是在BaseMatcher中的describeMismatch方法輸出的,通過這個信息明顯不清楚到底實際上得到了什麼User,因此在我們的Matcher中從寫這個方法:

/**
 * 當斷言失敗時,描述實際上得到的錯誤的對象。
 */
@Override
public void describeMismatch(Object item, Description description) {
    if (item == null) {
        description.appendText("was null");
    } else {
        User user = (User) item;
        description.appendText("was a common user (")
                .appendText("username: ").appendText(user.getUsername()).appendText(", ")
                .appendText("password: ").appendText(user.getPassword()).appendText(")");
    }
}

 重新執行測試,得到以下失敗信息:

java.lang.AssertionError: 
Expected: Administrator with 'admin' as username and password
     but: was a common user (username: haibin369, password: 123456)

 雖然我們自定義的Matcher已經能夠執行了,但是assertThat(user, new IsAdminMatcher());這段代碼並沒有達到之前所說的可讀性更好的要求,因此,我們仿照org.hamcrest.CoreMatchers,創建一個類去創建我們自定義的Matcher:

public class MyMatchers {
    public static Matcher<User> isAdmin() {
        return new IsAdminMatcher();
    }
}

在測試方法中靜態導入該類中的所有內容,則可以像下面一樣使用assertThat:

import static org.haibin369.matcher.MyMatchers.*;

public class AssertThatTest {

    User user = new User("haibin369", "123456");

    @Test
    public void testAdmin() throws Exception {
        assertThat(user, isAdmin());
    }
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章