Hamcrest断言

判断是不是,有没有

用户入口


MatcherAssert中的assertThat系列方法是Hamcrest的入口
用户可以直接给出一个布尔表达式,这种没什么好说,核心还是在于Matcher的使用
Matcher描述了期待值,它会判断实际值actual是否满足
如果不满足,assertThat会给出原因描述

MatcherAssert.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MatcherAssert {
public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
assertThat("", actual, matcher);
}

public static <T> void assertThat(String reason, T actual, Matcher<? super T> matcher) {
if (!matcher.matches(actual)) {
Description description = new StringDescription();
description.appendText(reason)
.appendText("\nExpected: ")
.appendDescriptionOf(matcher)
.appendText("\n but: ");
matcher.describeMismatch(actual, description);
throw new AssertionError(description.toString());
}
}

public static void assertThat(String reason, boolean assertion) {
if (!assertion) {
throw new AssertionError(reason);
}
}
}

匹配器职责和框架实现


匹配器的职责比较直观:判断+解释
判断对象是不是匹配是最最核心的功能
同时解释也是必要的,每个匹配器实现SelfDescribing接口描述自己的功能,在判断不匹配时也会给出原因

SelfDescribing.java Matcher.java
1
2
3
4
5
6
7
8
public interface SelfDescribing {
void describeTo(Description description);
}
public interface Matcher<T> extends SelfDescribing {
boolean matches(Object item);

void describeMismatch(Object item, Description mismatchDescription);
}

实际的比较器有个公共基类,主要负责一些描述信息的处理

BaseMatcher.java
1
2
3
4
5
6
7
8
9
10
11
public abstract class BaseMatcher<T> implements Matcher<T> {
@Override
public void describeMismatch(Object item, Description description) {
description.appendText("was ").appendValue(item);
}

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

匹配器实现


以最常见的判断相等的IsEqual匹配器为例
判断相等的实现比较直观,就是特殊考虑了数组相等,此外就是原生的equals函数
亮点在静态的工厂方法equalTo。日常基本都是使用静态方法,静态方法创建了匹配器实例。
静态方法还取了一个语义上更好的名字,为以后的匹配器包装做准备。

IsEqual.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
26
27
28
29
30
31
32
33
34
35
36
public class IsEqual<T> extends BaseMatcher<T> {
private final Object expectedValue;

public IsEqual(T equalArg) {
expectedValue = equalArg;
}

@Override
public boolean matches(Object actualValue) {
return areEqual(actualValue, expectedValue);
}

@Override
public void describeTo(Description description) {
description.appendValue(expectedValue);
}

private static boolean areEqual(Object actual, Object expected) {
if (actual == null) {
return expected == null;
}

if (expected != null && isArray(actual)) {
return isArray(expected) && areArraysEqual(actual, expected);
}

return actual.equals(expected);
}

//...

@Factory
public static <T> Matcher<T> equalTo(T operand) {
return new IsEqual<T>(operand);
}
}

匹配器包装


有的匹配器支持与其他匹配器组合,以取反操作IsNot匹配器为例
典型的装饰器模式,内部包含了其他匹配器实例,判断时加入取反逻辑
同样提供静态的工厂方法,接受匹配器,返回匹配器,使链式包装成为可能

IsNot.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
26
27
public class IsNot<T> extends BaseMatcher<T>  {
private final Matcher<T> matcher;

public IsNot(Matcher<T> matcher) {
this.matcher = matcher;
}

@Override
public boolean matches(Object arg) {
return !matcher.matches(arg);
}

@Override
public void describeTo(Description description) {
description.appendText("not ").appendDescriptionOf(matcher);
}

@Factory
public static <T> Matcher<T> not(Matcher<T> matcher) {
return new IsNot<T>(matcher);
}

@Factory
public static <T> Matcher<T> not(T value) {
return not(equalTo(value));
}
}

可用匹配器


匹配器类 静态方法 含义
终端匹配器
IsEqual equalTo 相等
IsAnything anything 总是满足
IsInstanceOf instanceOf 是类的实例
IsNull nullValue 是null
简写 notNullValue 等同not(nullValue(…))
IsSame sameInstance 同一对象
StringContains containsString 字符串包含
StringStartsWith startsWith 字符串包含前缀
StringEndsWith endsWith 字符串包含后缀
组合单个匹配器
Is is
简写 isA 等同is(instanceOf(…))
IsNot not
Every everyItem 集合每个满足
IsCollectionContaining hasItem 集合包含
简写 hasItem 等同hasItem(equalTo(…))
组合多个匹配器
AllOf allOf 满足所有
AnyOf anyOf 满足任意
IsCollectionContaining hasItems 集合包含多个
简写 hasItems allOf(hasItem(…),hasItem(…)…)

例子


即使写的条件比较复杂,可读性还可以

1
2
MatcherAssert.assertThat(Arrays.asList("1", "2"),
allOf(everyItem(not(startsWith("0"))), hasItem(equalTo("1"))));