單元測(cè)試——Hamcrest匹配器框架

一察滑、Hamcrest是什么?

Hamcrest is a library of matchers, which can be combined in to create flexible expressions of intent in tests.

Hamcrest 是一個(gè)為了測(cè)試為目的绑改,且能組合成靈活表達(dá)式的匹配器類庫(kù)。

二、為什么要用Hamcrest匹配器框架

Hamcrest的目標(biāo)是使測(cè)試盡可能的提高可讀性.例如is()方法其實(shí)就是equalTo()的包裝方法.

三涉瘾、常用方法介紹

    @Test
    public void testHamcrestMatchers() {
        // 核心匹配
        // allOf: 所有條件都必須滿足,相當(dāng)于&&
        assertThat("myname", allOf(startsWith("my"), containsString("name")));
        // anyOf: 其中一個(gè)滿足就通過(guò)捷兰, 相當(dāng)于||
        assertThat("myname", anyOf(startsWith("na"), containsString("name")));
        // both: &&
        assertThat("myname", both(containsString("my")).and(containsString("me")));
        // either: 兩者之一
        assertThat("myname", either(containsString("my")).or(containsString("you")));
        // everyItem: 每個(gè)元素都需滿足特定條件
        assertThat(Arrays.asList("my", "mine"), everyItem(startsWith("m")));
        // hasItem: 是否有這個(gè)元素
        assertThat(Arrays.asList("my", "mine"), hasItem("my"));
        // hasItems: 包含多個(gè)元素
        assertThat(Arrays.asList("my", "mine", "your"), hasItems("your", "my"));
        // is: is(equalTo(x))或is(instanceOf(clazz.class))的簡(jiǎn)寫
        assertThat("myname", is("myname"));
        assertThat("mynmae", is(String.class));
        // anything(): 任何情況下立叛,都匹配正確
        assertThat("myname", anything());
        // not: 否為真,相當(dāng)于贡茅!
        assertThat("myname", is(not("you")));
        // nullValue(): 值為空
        String str = null;
        assertThat(str, is(nullValue()));
        // notNullValue(): 值不為空
        String str2 = "123";
        assertThat(str2, is(notNullValue()));
 
        
        // 字符串匹配
        // containsString:包含字符串
        assertThat("myname", containsString("na"));
        // stringContainsInOrder: 順序包含秘蛇,“my”必須在“me”前面
        assertThat("myname", stringContainsInOrder(Arrays.asList("my", "me")));
        // endsWith: 后綴
        assertThat("myname", endsWith("me"));
        // startsWith: 前綴
        assertThat("myname", startsWith("my"));
        // isEmptyString(): 空字符串
        assertThat("", isEmptyString());
        // equalTo: 值相等, Object.equals(Object)
        assertThat("myname", equalTo("myname"));
        assertThat(new String[] {"a", "b"}, equalTo(new String[] {"a", "b"}));
        // equalToIgnoringCase: 比較時(shí)顶考,忽略大小寫
        assertThat("myname", equalToIgnoringCase("MYNAME"));
        // equalToIgnoringWhiteSpace: 比較時(shí)赁还, 首尾空格忽略, 比較時(shí)中間用單個(gè)空格
        assertThat(" my \t name ", equalToIgnoringWhiteSpace(" my name "));
        // isOneOf: 是否為其中之一
        assertThat("myname", isOneOf("myname", "yourname"));
        // isIn: 是否為其成員
        assertThat("myname", isIn(new String[]{"myname", "yourname"}));
        // toString() 返回值校驗(yàn)
        assertThat(333, hasToString(equalTo("333")));
       
        
        // 數(shù)值匹配
        // closeTo: [operand-error, operand+error], Double或BigDecimal類型
        assertThat(3.14, closeTo(3, 0.5));
        assertThat(new BigDecimal("3.14"), is(closeTo(new BigDecimal("3"), new BigDecimal("0.5"))));
        // comparesEqualTo: compareTo比較值
        assertThat(2, comparesEqualTo(2));
        // greaterThan: 大于
        assertThat(2, greaterThan(0));
        // greaterThanOrEqualTo: 大于等于
        assertThat(2, greaterThanOrEqualTo(2));
        // lessThan: 小于
        assertThat(0, lessThan(2));
        // lessThanOrEqualTo: 小于等于
        assertThat(0, lessThanOrEqualTo(0));
        
        
        
        // 集合匹配
        // array: 數(shù)組長(zhǎng)度相等且對(duì)應(yīng)元素也相等
        assertThat(new Integer[]{1, 2, 3}, is(array(equalTo(1), equalTo(2), equalTo(3))));
        // hasItemInArray: 數(shù)組是否包含特定元素
        assertThat(new String[]{"my", "you"}, hasItemInArray(startsWith("y")));
        // arrayContainingInAnyOrder驹沿, 順序無(wú)關(guān)艘策,長(zhǎng)度要一致
        assertThat(new String[]{"my", "you"}, arrayContainingInAnyOrder("you", "my"));
        // arrayContaining:  順序,長(zhǎng)度一致
        assertThat(new String[]{"my", "you"}, arrayContaining("my", "you"));
        // arrayWithSize: 數(shù)組長(zhǎng)度
        assertThat(new String[]{"my", "you"}, arrayWithSize(2));
        // emptyArray: 空數(shù)組
        assertThat(new String[0], emptyArray());
        // hasSize: 集合大小
        assertThat(Arrays.asList("my", "you"), hasSize(equalTo(2)));
        // empty: 空集合
        assertThat(new ArrayList<String>(), is(empty()));
        // isIn: 是否為集合成員
        assertThat("myname", isIn(Arrays.asList("myname", "yourname")));
       // Map匹配
        Map<String, String> myMap = new HashMap<String, String>();
        myMap.put("name", "john");
        // hasEntry: key && value匹配
        assertThat(myMap, hasEntry("name", "john"));
        // hasKey: key匹配
        assertThat(myMap, hasKey(equalTo("name")));
        // hasValue: value匹配
        assertThat(myMap, hasValue(equalTo("john")));
    }

詳細(xì)請(qǐng)看: Hamcrest API

四甚负、自定義Hamcrest匹配器

1.通過(guò)FeatureMatcher自定義Hamcrest匹配器

創(chuàng)建Hamcrest匹配器

我們自定義一個(gè)為String提供長(zhǎng)度的匹配器,需要利用FeatureMatcher類,封裝一個(gè)現(xiàn)有的匹配器,用來(lái)決定給定的被測(cè)對(duì)象的哪個(gè)字段匹配,并且提供豐富的錯(cuò)誤信息.FeatureMatcher的構(gòu)造函數(shù)有下列參數(shù):

  • 我們想要包裝的匹配器
  • 對(duì)我們測(cè)試的功能的描述(在錯(cuò)誤信息會(huì)有體現(xiàn))
  • 測(cè)試功能的名字(在錯(cuò)誤信息會(huì)有體現(xiàn))

我們必須重寫featureValueOf(T actual),它的返回值將傳入matchesSafely()方法進(jìn)行匹配判斷.

public static Matcher<String> length(Matcher<? super Integer> matcher) {
    return new FeatureMatcher<String, Integer>(matcher,
            "a String of length that", "length") {
        @Override
        protected Integer featureValueOf(String actual) {
            return actual.length();
        }
    };
}
測(cè)試

使用你剛才創(chuàng)建的自定義匹配器驗(yàn)證"Gandalf"的長(zhǎng)度為8

@Test
public void fellowShipOfTheRingShouldContainer7() {
    assertThat("Gandalf", length(is(8)));
}

使用TypeSafeMatcher自定義匹配器

我們可以對(duì)TypeSafeMatcher進(jìn)行擴(kuò)展.與BaseMatcher相比TypeSafeMatcher可以自動(dòng)的檢查null值, 在被委派到matchesSafely()方法之前檢查類型并進(jìn)行適當(dāng)?shù)霓D(zhuǎn)換.下面定義了一個(gè)檢查一個(gè)字符串是否匹配正則關(guān)系的匹配器.

public class RegexMatcher extends TypeSafeMatcher<String> {
    private final String regex;
    public RegexMatcher(final String regex) {
        this.regex = regex;
    }
    @Override
    public void describeTo(final Description description) {
        description.appendText("matches regular expression=`" + regex + "`");
    }
    @Override
    public boolean matchesSafely(final String string) {
        return string.matches(regex);
    }
    // matcher method you can call on this matcher class
    public static RegexMatcher matchesRegex(final String regex) {
        return new RegexMatcher(regex);
    }
}
測(cè)試
@Test
public void testRegularExpressionMatcher() throws Exception {
    String s ="aaabbbaaaa";
    assertThat(s, RegexMatcher.matchesRegex("a*b*a*"));
}

自定義組合匹配器

為什么要自定義組合匹配器

Hamcrest有內(nèi)置的組合匹配器,但是它的可讀性太差!

下面就是一個(gè)案例:

@Test
public void testCombining() {
    List<Integer> list = new ArrayList<>();
    assertThat(list, both(hasSize(1)).and(contains(42)));
}

可讀性差,無(wú)法準(zhǔn)確描述錯(cuò)誤信息.

創(chuàng)建自定義組合匹配器

我們可以繼承BaseMatchers類使用它提供對(duì)外連接的方法(matches),本身再提供一個(gè)添加方法(add).將匹配器鏈接起來(lái).并保存在集合中.

public class MatcherCombinator<T> extends BaseMatcher<T> {
    private final List<Matcher<? super T>> matchers = new ArrayList<>();
    private final List<Matcher<? super T>> failedMatchers = new ArrayList<>();

    private MatcherCombinator(final Matcher<? super T> matcher) {
        matchers.add(matcher);
    }

    public MatcherCombinator<T> and(final Matcher<? super T> matcher) {
        matchers.add(matcher);
        return this;
    }

    @Override
    public boolean matches(final Object item) {
        boolean matchesAllMatchers = true;
        for (final Matcher<? super T> matcher : matchers) {
            if (!matcher.matches(item)) {
                failedMatchers.add(matcher);
                matchesAllMatchers = false;
            }
        }
        return matchesAllMatchers;
    }

    @Override
    public void describeTo(final Description description) {
        description.appendValueList("\n", " " + "and" + "\n", "", matchers);
    }

    @Override
    public void describeMismatch(final Object item, final Description description) {
        description.appendText("\n");
        for (Iterator<Matcher<? super T>> iterator = failedMatchers.iterator(); iterator.hasNext();) {
            final Matcher<? super T> matcher = iterator.next();
            description.appendText("Expected: <");
            description.appendDescriptionOf(matcher).appendText(" but ");
            matcher.describeMismatch(item, description);
            if (iterator.hasNext()) {
                description.appendText(">\n");
            }
        }
    }

    public static <LHS> MatcherCombinator<LHS> matches(final Matcher<? super LHS> matcher) {
        return new MatcherCombinator<LHS>(matcher);
    }
}
測(cè)試
@Test
public void testCustomCombining() {
    List<Integer> list = new ArrayList<>();
    assertThat(list, MatcherCombinator.matches(hasSize(1)).and(contains(42)));
}

引用:
引用1
引用2
引用3

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柬焕,一起剝皮案震驚了整個(gè)濱河市审残,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斑举,老刑警劉巖搅轿,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異富玷,居然都是意外死亡璧坟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門赎懦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)雀鹃,“玉大人,你說(shuō)我怎么就攤上這事励两±杈ィ” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵当悔,是天一觀的道長(zhǎng)傅瞻。 經(jīng)常有香客問(wèn)我,道長(zhǎng)盲憎,這世上最難降的妖魔是什么嗅骄? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮饼疙,結(jié)果婚禮上溺森,老公的妹妹穿的比我還像新娘。我一直安慰自己窑眯,他們只是感情好屏积,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著磅甩,像睡著了一般肾请。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上更胖,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天铛铁,我揣著相機(jī)與錄音,去河邊找鬼却妨。 笑死饵逐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的彪标。 我是一名探鬼主播倍权,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了薄声?” 一聲冷哼從身側(cè)響起当船,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎默辨,沒(méi)想到半個(gè)月后德频,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缩幸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年壹置,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片表谊。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钞护,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出爆办,到底是詐尸還是另有隱情难咕,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布距辆,位于F島的核電站步藕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏挑格。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一沾歪、第九天 我趴在偏房一處隱蔽的房頂上張望漂彤。 院中可真熱鬧,春花似錦灾搏、人聲如沸挫望。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)媳板。三九已至,卻和暖如春泉哈,著一層夾襖步出監(jiān)牢的瞬間蛉幸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工丛晦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奕纫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓烫沙,卻偏偏與公主長(zhǎng)得像匹层,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锌蓄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355