Android 單元測試第六篇(Hamcrest 匹配器)

在單元測試過程結(jié)束后买决,我們期望編譯器可以直接告訴我們是 fail 還是 success莲绰。那么如何判斷某個 case 是否通過,就尤為重要覆旭,如果只是簡單的使用 assertEqual退子、assertFalse、assertNull型将、assertNotEquals寂祥、assertSame,那么很多情況就判斷不了七兜,比如判斷某個集合是否包含某個元素丸凭,某個字符串是否以"Man"開頭,這個時候我們就需要搬出匹配器了。

匹配器簡介

其實匹配器就是內(nèi)部采用了特定的算法惜犀,來實現(xiàn)特定的業(yè)務判斷铛碑,比如 startsWith("Man")返回的就是的就是一個用來判斷某個字符串是否以Man 開頭的字符串。日常中還是有很多匹配器是很常見的虽界,所以就有這么一個包含大量常見匹配器的框架汽烦,叫做Hamcrest,該框架結(jié)合Junit用起來確實很棒莉御,所以從Junit 4.11開始撇吞,Junit已經(jīng)默認依賴了Hamcrest,以Junit4.12為例子礁叔,內(nèi)部依賴的就是Hamcrest-core:1.3牍颈。

如何在單元測試中使用Hamcrest呢?其實很簡單琅关,只要使用以下這個斷言即可

public static <T> void assertThat(T actual, Matcher<? super T> matcher);
集成 Hamcrest

上面介紹了自從Junit 4.11開始煮岁,就已經(jīng)自動依賴了,但是為什么本節(jié)還要講集成呢涣易?原因有下面兩點

  1. 默認集成的是Hamcrest-core:1.3画机,常用的匹配器方法被封裝在幾十個類中,這樣我們使用一些靜態(tài)方法會很麻煩都毒,需要一個一個導包色罚,如圖一所示:

    圖一.png

  2. core只是包含了最常用的一些匹配器,像數(shù)組账劲、字典、數(shù)值之類的大部分匹配器是沒有的金抡,但這類匹配器我們?nèi)粘i_發(fā)中使用到的場景也不少瀑焦。

所以我們需要集成全部的匹配器,我們在app/build.gradle中添加如下依賴

testImplementation 'junit:junit:4.12'
testImplementation 'org.hamcrest:hamcrest-all:1.3'

然后在使用到的單元測試類或者測試基類中導入所有匹配器梗肝,這樣我們就不需要想圖一一樣每用一個需要導入一個榛瓮,而且你還需要準確的記得每個匹配器的名字,不然是沒有智能提示的巫击。

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
Hamcrest的使用

Hamcrest的匹配器用起來確實很簡單禀晓,所以直接上常用幾十匹配器的例子,大多數(shù)通過看名字就知道匹配器的作用坝锰,一些可能造成誤解的我也都寫了注釋粹懒,有時間的伙伴可以敲一遍或者瀏覽一下,有個印象顷级,等用到的時候可以再查看凫乖。

// hamcrest-core
@Test
public void testHamCrest() {

    //JUnit 4.11 and later 自動集成 core
    //基礎操作
    //1. 字符串相關
    assertThat("myValue", startsWith("my"));
    assertThat("myValue", containsString("Val"));
    assertThat("myValue", endsWith("e"));
    assertThat("myValue", equalTo("myValue"));
    assertThat("myValue", anything()); //怎樣都是通過
    assertThat("myValue", anything("怎樣都是通過"));
    //2. 定義相關,比如某位是什么,不是什么帽芽,等于什么,不等于什么
    assertThat(1, equalTo(1));
    assertThat("myValue", instanceOf(String.class));
    assertThat(1, not(2));
    assertThat(null, nullValue());
    assertThat("myValue", notNullValue());
    assertThat("myValue", sameInstance("myValue")); //和 theInstance(T)一樣

    //3. 集合相關 Iterable
    //3.1 everyItem 每個 item 都要符合條件
    assertThat(Arrays.asList("bar", "baz"), everyItem(startsWith("ba")));
    //3.2 hasItem 至少有一個 item 都符合條件删掀,或者集合中有這個 item,參數(shù)可以是 T 也可以是匹配器
    assertThat(Arrays.asList("foo", "bar"), hasItem("bar"));
    assertThat(Arrays.asList("foo", "bar"), hasItem(startsWith("fo")));
    //3.3 hasItems 是hasItem復數(shù)版本导街,支持多 T 類型參數(shù)和多匹配器參數(shù)
    assertThat(Arrays.asList("foo", "bar", "baz"), hasItems("baz", "foo"));
    assertThat(Arrays.asList("foo", "bar", "baz"), hasItems(endsWith("z"), endsWith("o")));


    //組合匹配器披泪,一般都支持多個參數(shù),雖然下面的提供的是使用兩個參數(shù)的例子
    //1. allOf 全部條件都需要滿足
    assertThat("myValue", allOf(startsWith("my"), containsString("Val")));
    //2. anyOf 滿足其中一個條件即可
    assertThat("myValue", anyOf(startsWith("foo"), containsString("Val")));
    //3. both().and() 滿足兩個條件搬瑰,為 allOf 的真子集
    assertThat("fab", both(containsString("a")).and(containsString("b")));
    //4. either().or() 滿足一個條件款票,為 anyOf 的真子集
    assertThat("fab", either(containsString("a")).or(containsString("b")));



    //輔助斷言,對于機器來說沒什么用跌捆,只是讓語句讀起來更加像自然語言
    //1. describedAs 增加斷言輔助描述徽职,增強可讀性,一旦斷言不通過佩厚,將直接打印描述內(nèi)容到控制臺姆钉。
    //比如下面這個例子,看完之后我們就知道這個斷言輔助描述是想告訴我們?yōu)槭裁雌诖闹凳?.
    //等同于 assertThat(2, equalTo(2));
    assertThat(2, describedAs("1 + 1 must equal 2", equalTo(2)));

    //2. is 又是一個語法糖抄瓦,增加可讀性而已
    assertThat("foo", is(equalTo("foo")));
    //2.0 如果里面的匹配器是 equalTo潮瓶,則可以簡寫
    assertThat("foo", is("foo"));

    //3. isA 又是一個語法糖,不過參數(shù)只能是 Class<T>
    //其實就是assertThat("foo", is(instanceOf(String.class)))的簡寫
    assertThat("foo", isA(String.class));


    //自定義匹配器, 繼承自CustomMatcher,實現(xiàn) matches 方法即可
    Matcher<String> aNonEmptyString = new CustomMatcher<String>("a non empty string") {
        public boolean matches(Object object) {
            return (object instanceof String) && !((String) object).isEmpty();
        }
    };
    assertThat("foo", aNonEmptyString);

 }

以上是core部分钙姊,意思就是使用Junit 4.12自帶依賴的Hamcrest即可使用毯辅,不過需要手動導包,而且是很多包煞额,下面補充一下其它常用的匹配器思恐,屬于core之外的了。

// hamcrest-all
@Test
public void hamcrestAll() throws Exception {
    //array,針對數(shù)組每一項進行測試 each matcher[i] is satisfied by array[i]膊毁,條件成立僅當匹配器個數(shù)等于數(shù)組元素個數(shù)胀莹,且每個匹配器都通過
    //數(shù)組類型
    assertThat(new Integer[]{1,2,3}, is(array(equalTo(1), equalTo(2), equalTo(3))));
    //包含所有內(nèi)容,不需要按照順序婚温,和array不一樣
    assertThat(new Integer[]{1,2,3}, arrayContainingInAnyOrder(3, 2, 1));
    assertThat(new String[] {"foo", "bar"}, hasItemInArray(startsWith("ba")));
    assertThat(new Integer[]{1,2,3}, arrayWithSize(3)); //對應 Collection 類型的 hasSize()
    assertThat(new String[0], emptyArray()); //對應 Collection 的 empty()

    //Iterable類型也有上面相應的 API描焰,下面舉兩個例子
    assertThat(Arrays.asList("foo", "bar"), hasSize(2));
    assertThat(new ArrayList<String>(), is(empty()));

    //map類型
    HashMap<String, String> map = new HashMap<>();
    map.put("bar", "foo");
    map.put("name", "Mango");
    //是否包含特定鍵值對
    assertThat(map, hasEntry("bar", "foo"));
    assertThat(map, hasEntry(equalTo("bar"), equalTo("foo")));
    assertThat(map, hasKey(equalTo("bar")));
    assertThat(map, hasValue(equalTo("foo")));

    //期望的值是否屬于某個集合
    assertThat("foo", isIn(Arrays.asList("bar", "foo")));

    //double 類型
    //誤差在正負0.04內(nèi)算通過
    assertThat(1.03, is(closeTo(1.0, 0.04)));
    assertThat(2, greaterThan(1));
    assertThat(1, greaterThanOrEqualTo(1));
    assertThat(1, lessThan(2));
    assertThat(1, lessThanOrEqualTo(1));

    //text 類型
    assertThat("Foo", equalToIgnoringCase("FOO"));
    assertThat("   my\tfoo  bar ", equalToIgnoringWhiteSpace(" my  foo bar"));
    assertThat("", isEmptyString());
    assertThat(null, isEmptyOrNullString());
}

例子到這里就結(jié)束了,最后再附上官方文檔栅螟。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荆秦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子力图,更是在濱河造成了極大的恐慌步绸,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搪哪,死亡現(xiàn)場離奇詭異靡努,居然都是意外死亡坪圾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門惑朦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兽泄,“玉大人,你說我怎么就攤上這事漾月〔∩遥” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵梁肿,是天一觀的道長蜓陌。 經(jīng)常有香客問我,道長吩蔑,這世上最難降的妖魔是什么钮热? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮烛芬,結(jié)果婚禮上隧期,老公的妹妹穿的比我還像新娘。我一直安慰自己赘娄,他們只是感情好仆潮,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遣臼,像睡著了一般性置。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上揍堰,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天鹏浅,我揣著相機與錄音,去河邊找鬼屏歹。 笑死篡石,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的西采。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼继控,長吁一口氣:“原來是場噩夢啊……” “哼械馆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起武通,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤霹崎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冶忱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尾菇,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了派诬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劳淆。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖默赂,靈堂內(nèi)的尸體忽然破棺而出沛鸵,到底是詐尸還是另有隱情,我是刑警寧澤缆八,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布曲掰,位于F島的核電站,受9級特大地震影響奈辰,放射性物質(zhì)發(fā)生泄漏栏妖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一奖恰、第九天 我趴在偏房一處隱蔽的房頂上張望吊趾。 院中可真熱鬧,春花似錦房官、人聲如沸趾徽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孵奶。三九已至,卻和暖如春蜡峰,著一層夾襖步出監(jiān)牢的瞬間了袁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工湿颅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留载绿,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓油航,卻偏偏與公主長得像崭庸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谊囚,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容