Android 单元测试第六篇(Hamcrest 匹配器)
在单元测试过程结束后,我们期望编译器可以直接告诉我们是 fail 还是 success。那么如何判断某个 case 是否通过,就尤为重要,如果只是简单的使用 assertEqual、assertFalse、assertNull、assertNotEquals、assertSame,那么很多情况就判断不了,比如判断某个集合是否包含某个元素,某个字符串是否以"Man"开头,这个时候我们就需要搬出匹配器了。
匹配器简介
其实匹配器就是内部采用了特定的算法,来实现特定的业务判断,比如 startsWith("Man")
返回的就是的就是一个用来判断某个字符串是否以Man
开头的字符串。日常中还是有很多匹配器是很常见的,所以就有这么一个包含大量常见匹配器的框架,叫做Hamcrest
,该框架结合Junit
用起来确实很棒,所以从Junit 4.11
开始,Junit
已经默认依赖了Hamcrest
,以Junit4.12
为例子,内部依赖的就是Hamcrest-core:1.3
。
如何在单元测试中使用Hamcrest
呢?其实很简单,只要使用以下这个断言即可
public static <T> void assertThat(T actual, Matcher<? super T> matcher);
集成 Hamcrest
上面介绍了自从Junit 4.11
开始,就已经自动依赖了,但是为什么本节还要讲集成呢?原因有下面两点
-
默认集成的是
图一.pngHamcrest-core:1.3
,常用的匹配器方法被封装在几十个类中,这样我们使用一些静态方法会很麻烦,需要一个一个导包,如图一所示:
-
core
只是包含了最常用的一些匹配器,像数组、字典、数值之类的大部分匹配器是没有的,但这类匹配器我们日常开发中使用到的场景也不少。
所以我们需要集成全部的匹配器,我们在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
的匹配器用起来确实很简单,所以直接上常用几十匹配器的例子,大多数通过看名字就知道匹配器的作用,一些可能造成误解的我也都写了注释,有时间的伙伴可以敲一遍或者浏览一下,有个印象,等用到的时候可以再查看。
// 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,参数可以是 T 也可以是匹配器
assertThat(Arrays.asList("foo", "bar"), hasItem("bar"));
assertThat(Arrays.asList("foo", "bar"), hasItem(startsWith("fo")));
//3.3 hasItems 是hasItem复数版本,支持多 T 类型参数和多匹配器参数
assertThat(Arrays.asList("foo", "bar", "baz"), hasItems("baz", "foo"));
assertThat(Arrays.asList("foo", "bar", "baz"), hasItems(endsWith("z"), endsWith("o")));
//组合匹配器,一般都支持多个参数,虽然下面的提供的是使用两个参数的例子
//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 增加断言辅助描述,增强可读性,一旦断言不通过,将直接打印描述内容到控制台。
//比如下面这个例子,看完之后我们就知道这个断言辅助描述是想告诉我们为什么期待的值是2.
//等同于 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 又是一个语法糖,不过参数只能是 Class<T>
//其实就是assertThat("foo", is(instanceOf(String.class)))的简写
assertThat("foo", isA(String.class));
//自定义匹配器, 继承自CustomMatcher,实现 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,针对数组每一项进行测试 each matcher[i] is satisfied by array[i],条件成立仅当匹配器个数等于数组元素个数,且每个匹配器都通过
//数组类型
assertThat(new Integer[]{1,2,3}, is(array(equalTo(1), equalTo(2), equalTo(3))));
//包含所有内容,不需要按照顺序,和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内算通过
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());
}
例子到这里就结束了,最后再附上官方文档。