初次用文字的方式记录读源码的过程,不知道怎么写,感觉有点贴代码的嫌疑。不过中间还是加入了一些自己的理解和心得,希望以后能够慢慢的改进,感兴趣的童鞋凑合着看吧,感觉JUnit这个框架还是值得看的,里面有许多不错的设计思想在,更何况它是Kent Beck和Erich Gamma这样的大师写的。。。。。
深入JUnit源码之Assert与Hamcrest
到目前,JUnit4所有的核心源码都已经讲解过了,最后剩下的就是为了兼容性而引入的和JUnit3相关的代码以及Assert中的代码。本节将关注于Assert代码。在JUnit4中,对Assert还引入了hamcrest框架的支持,以使断言可读性更好,并且也具有了一定的扩展性。因而本文还会对hamcrest框架做一个简单的介绍。
普通Assert实现
Assert类是JUnit提供的一些断言方法,以供测试方法判断测试逻辑是否符合预期。它主要提供六类方法:
1. 判断对象是否相等。若不相等,一般抛出:“${message} expected: <${expectedToString}> but was: <${actualToString}>”作为error message的AssertionError的Exception。如果expectedToString的值和actualToString的值相等,则error message变为:“${message}: expected: ${expectedClassName}<${expectedToString}> but was: ${actualClassName}<${actualToString}>”。
当expected和actual都是String类型时,ComparisonFailure还会找出是前后相同的串,并用[Different String]标明那些不相同的字符串,也就是expectedToString和actualToString的格式将会变成:…${sameString}[${differentString}]${sameString}…。其中“…”只会在相同的字符串太长的情况下才会出现,这个长度标准目前(JUnit4.10)是20个字符。具体实现参考ComparisonFailure类,它继承自AssertionError,这里不再展开。
1 static public void assertEquals(String message, Object expected,
2 Object actual) {
3 if (expected == null && actual == null)
4 return;
5 if (expected != null && isEquals(expected, actual))
6 return;
7 else if (expected instanceof String && actual instanceof String) {
8 String cleanMessage= message == null ? "" : message;
9 throw new ComparisonFailure(cleanMessage, (String) expected,
10 (String) actual);
11 } else
12 failNotEquals(message, expected, actual);
13 }
14 private static boolean isEquals(Object expected, Object actual) {
15 return expected.equals(actual);
16 }
17 static private void failNotEquals(String message, Object expected,
18 Object actual) {
19 fail(format(message, expected, actual));
20 }
21 static String format(String message, Object expected, Object actual) {
22 String formatted= "";
23 if (message != null && !message.equals(""))
24 formatted= message + " ";
25 String expectedString= String.valueOf(expected);
26 String actualString= String.valueOf(actual);
27 if (expectedString.equals(actualString))
28 return formatted + "expected: "
29 + formatClassAndValue(expected, expectedString)
30 + " but was: " + formatClassAndValue(actual, actualString);
31 else
32 return formatted + "expected:<" + expectedString + "> but was:<"
33 + actualString + ">";
34 }
35 private static String formatClassAndValue(Object value, String valueString) {
36 String className= value == null ? "null" : value.getClass().getName();
37 return className + "<" + valueString + ">";
38 }
39 //对于其他方法,只是对上面方法的包装
40 static public void assertEquals(Object expected, Object actual) {
41 assertEquals(null, expected, actual);
42 }
43 static public void assertEquals(long expected, long actual) {
44 assertEquals(null, expected, actual);
45 }
46 static public void assertEquals(String message, long expected, long actual) {
47 assertEquals(message, (Long) expected, (Long) actual);
48 }
49 //对于double、float类型有点特殊,因为由于浮点的精度问题,有些时候我们允许测试逻辑返回的结果和预计有差别,这种情况有delta表达,而且double和float都有一些特殊值,因而不能直接用“==”来比较,而需要用Double.compare()方法比较,这个方法对都是Double.NaN放回相等。
50 static public void assertEquals(String message, double expected,
51 double actual, double delta) {
52 if (Double.compare(expected, actual) == 0)
53 return;
54 if (!(Math.abs(expected - actual) <= delta))
55 failNotEquals(message, new Double(expected), new Double(actual));
56 }
57 static public void assertEquals(double expected, double actual, double delta) {
58 assertEquals(null, expected, actual, delta);
59 }
2. 判断数组是否相等(支持多维数组)。如果因为expecteds或actuals有一个为null而不等,抛出的AssertionError消息为:${message}: expected(actual) array was null。如果因为expecteds和actuals的长度不等,跑粗的AssertionError消息为:${message}: array lengths differed, expected.length=${expectedLength} actual.length=${actualLength}。如果因为内部元素不相等,抛出的AssertionError消息为:${message}: arrays first differed at element [i][j]….; ${notEqualMessage}。关于这段逻辑的详细实现参考ExactComparisonCriteria类。
1 public static void assertArrayEquals(String message, Object[] expecteds,
2 Object[] actuals) throws ArrayComparisonFailure {
3 internalArrayEquals(message, expecteds, actuals);
4 }
5 //这个方法可以为以后自己的设计做参考,它之所以采用Object表达一个数组是为了提高重用性,即这个方法可以用在double数组、int数组以及Object数组上,而方法内部可以使用Array提供的一些静态方法对内部数据做一些操作。
6 private static void internalArrayEquals(String message, Object expecteds,
7 Object actuals) throws ArrayComparisonFailure {
8 new ExactComparisonCriteria().arrayEquals(message, expecteds, actuals);
9 }
10 //对于其他方法,只是对上面方法的包装
11 public static void assertArrayEquals(Object[] expecteds, Object[] actuals) {
12 assertArrayEquals(null, expecteds, actuals);
13 }
14 public static void assertArrayEquals(String message, byte[] expecteds,
15 byte[] actuals) throws ArrayComparisonFailure {
16 internalArrayEquals(message, expecteds, actuals);
17 }
18 public static void assertArrayEquals(byte[] expecteds, byte[] actuals) {
19 assertArrayEquals(null, expecteds, actuals);
20 }
21 public static void assertArrayEquals(String message, char[] expecteds,
22 char[] actuals) throws ArrayComparisonFailure {
23 internalArrayEquals(message, expecteds, actuals);
24 }
25 public static void assertArrayEquals(char[] expecteds, char[] actuals) {
26 assertArrayEquals(null, expecteds, actuals);
27 }
28 public static void assertArrayEquals(String message, short[] expecteds,
29 short[] actuals) throws ArrayComparisonFailure {
30 internalArrayEquals(message, expecteds, actuals);
31 }
32 public static void assertArrayEquals(short[] expecteds, short[] actuals) {
33 assertArrayEquals(null, expecteds, actuals);
34 }
35 public static void assertArrayEquals(String message, int[] expecteds,
36 int[] actuals) throws ArrayComparisonFailure {
37 internalArrayEquals(message, expecteds, actuals);
38 }
39 public static void assertArrayEquals(int[] expecteds, int[] actuals) {
40 assertArrayEquals(null, expecteds, actuals);
41 }
42 public static void assertArrayEquals(String message, long[] expecteds,
43 long[] actuals) throws ArrayComparisonFailure {
44 internalArrayEquals(message, expecteds, actuals);
45 }
46 public static void assertArrayEquals(long[] expecteds, long[] actuals) {
47 assertArrayEquals(null, expecteds, actuals);
48 }
49 //对double和float逻辑有点特殊,因为他们的比较需要提供delta值,因而使用InexactComparisonCriteria,不过除了考虑delta因素,其他比较逻辑相同
50 public static void assertArrayEquals(String message, double[] expecteds,
51 double[] actuals, double delta) throws ArrayComparisonFailure {
52 new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals);
53 }
54 public static void assertArrayEquals(double[] expecteds, double[] actuals, double delta) {
55 assertArrayEquals(null, expecteds, actuals, delta);
56 }
57 public static void assertArrayEquals(String message, float[] expecteds,
58 float[] actuals, float delta) throws ArrayComparisonFailure {
59 new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals);
60 }
61 public static void assertArrayEquals(float[] expecteds, float[] actuals, float delta) {
62 assertArrayEquals(null, expecteds, actuals, delta);
63 }
3. 判断引用是否一样。若两个对象的引用不一样,则抛出的AssertionError消息为:${message} expected same:<${expected}> was not:<actual>。
1 static public void assertSame(String message, Object expected, Object actual) {
2 if (expected == actual)
3 return;
4 failNotSame(message, expected, actual);
5 }
6 static private void failNotSame(String message, Object expected,
7 Object actual) {
8 String formatted= "";
9 if (message != null)
10 formatted= message + " ";
11 fail(formatted + "expected same:<" + expected + "> was not:<" + actual
12 + ">");
13 }
14 static public void assertSame(Object expected, Object actual) {
15 assertSame(null, expected, actual);
16 }
17 static public void assertNotSame(String message, Object unexpected,
18 Object actual) {
19 if (unexpected == actual)
20 failSame(message);
21 }
22 static public void assertNotSame(Object unexpected, Object actual) {
23 assertNotSame(null, unexpected, actual);
24 }
25 static private void failSame(String message) {
26 String formatted= "";
27 if (message != null)
28 formatted= message + " ";
29 fail(formatted + "expected not same");
30 }
4. 判断值是否为null。null值的判断需要自己提供消息,不然不会打印具体消息。
1 static public void assertNotNull(String message, Object object) {
2 assertTrue(message, object != null);
3 }
4 static public void assertNotNull(Object object) {
5 assertNotNull(null, object);
6 }
7 static public void assertNull(String message, Object object) {
8 assertTrue(message, object == null);
9 }
10 static public void assertNull(Object object) {
11 assertNull(null, object);
12 }
5. 直接断言成功或失败。
1 static public void assertTrue(String message, boolean condition) {
2 if (!condition)
3 fail(message);
4 }
5 static public void assertTrue(boolean condition) {
6 assertTrue(null, condition);
7 }
8 static public void assertFalse(String message, boolean condition) {
9 assertTrue(message, !condition);
10 }
11 static public void assertFalse(boolean condition) {
12 assertFalse(null, condition);
13 }
14 static public void fail(String message) {
15 if (message == null)
16 throw new AssertionError();
17 throw new AssertionError(message);
18 }
19 static public void fail() {
20 fail(null);
21 }
6. 支持hamcrest框架的assertThat。assertThat()方法的引入是JUnit对hamcrest中Matcher的支持,其中matcher为一个expected值的matcher。在assertThat()中,判断matcher是否可以match传入的actual,若否,则打印出错信息。关于hamcrest中Matcher的详细信息见下一小节。
1 public static <T> void assertThat(T actual, Matcher<T> matcher) {
2 assertThat("", actual, matcher);
3 }
4 public static <T> void assertThat(String reason, T actual,
5 Matcher<T> matcher) {
6 if (!matcher.matches(actual)) {
7 Description description= new StringDescription();
8 description.appendText(reason);
9 description.appendText("\nExpected: ");
10 description.appendDescriptionOf(matcher);
11 description.appendText("\n got: ");
12 description.appendValue(actual);
13 description.appendText("\n");
14 throw new java.lang.AssertionError(description.toString());
15 }
16 }
使用assertThat引入hamcrest
说实话,我感觉到现在我可能还没有真正理解JUnit引入hamcrest意义。以我现在的理解,感觉引入hamcrest的Matcher有以下几点好处:
1. 提供了统一的编程接口,不管什么样验证逻辑,都可以使用assertThat()这个方法实现,只需要提供不同的Matcher即可。
2. 增加了可扩展性和重用性,因为Matcher可以用户自己定义,那么就相当于把匹配逻辑这一变量封装在了一个类中,这个类可以在不同的场景中替换,即可扩展性;而对一个类,又可以用在相似的场景中,即重用性。让我想起了一句关于面向对象设计的话:哪里变化封装哪里。
3. 增加了提示信息的可读性,这个完全取决于实现,只是Matcher提供了这样一种可能性,因为我们可以在自己的Matcher中自定义提示信息。
4. 增加了代码的可读性,因为Matcher可以以一种更接近自然语言的方式描述要实现的功能,如equalTo、is等。
不知道是否还有其他更加好或者说更根本的理由L。我一如既往的喜欢先画一个类结构图(hamcrest中的Matchers)。
线条太多了,图画的有点乱,之所以把那些所有对Matcher有引用的Matcher的引用关系画出来是为了表达hamcrest对Matcher的实现事实上用了Decorator设计模式。其中BaseMatcher下一层所有的Matcher(IsAnything、IsInstanceOf、IsEqual、IsSame、IsNull)是ConcreteComponent,而再下一层所有Matcher(Is、IsNot、DescribedAs、CombinableMatcher 、AnyOf、AllOf)都是Decorator。事实上,Decorator Matcher可以引用其他的Decorator Matcher,因而他们可以串在一起,在自己的类中实现自己的逻辑,而把其他逻辑交给其他Matcher,实现类似Chain Of Responsibility模式。模式看多了,越来越感觉很多模式之间都是想通的。
Matcher和BaseMatcher
Matcher是hamcrest框架的核心,其所有的实现都是围绕这个Matcher展开的。Matcher的主要功能是传入一个类实例,以判断该实例是否能和当前Matcher所定义的逻辑匹配,当出现不匹配的时,Matcher需要能描述能匹配自己的逻辑。因而Matcher中主要有两个方法,其中一个是match()方法,另一个则是describeTo()方法,将自己能匹配的逻辑描述到传入的Description中。hamcrest中规定所有的Matcher都要继承自BaseMatcher,从而在以后的版本变化中可以往Matcher中添加更多的接口,而不需要担心对之前版本的兼容性实现。为了强制这种规定,hamcrest设计者在Matcher接口中添加了_dont_implement_matcher___instea d_base_matcher()方法,这样当有用户想直接实现Matcher时,就需要实现这个方法,根据方法名他就可以得到警告,如果他忽略这个警告而直接在他自己的Matcher中实现了该方法,那么后果就需要由他自己承担。事实上这个设计的想法可以在一些框架实现中做一些参考。对目前hamcrest对BaseMatcher的实现比较简单,只是实现了上述的方法和toString()方法。
1 public interface Matcher<T> extends SelfDescribing {
2 boolean matches(Object item);
3 void _dont_implement_Matcher___instead_extend_BaseMatcher_();
4 }
5 public interface SelfDescribing {
6 void describeTo(Description description);
7 }
8 public abstract class BaseMatcher<T> implements Matcher<T> {
9 public final void _dont_implement_Matcher___instead_extend_BaseMatcher_() {
10 // See Matcher interface for an explanation of this method.
11 }
12 @Override
13 public String toString() {
14 return StringDescription.toString(this);
15 }
16 }
Description
由于Matcher的实现需要用到Description,因而这里首先对Description做一些介绍。在JUnit中,Description是对其中某个组件的描述,它可以是Runner、测试方法等。但是在hamcrest中,它是一个Collector,即它会遍历整个Matcher链,以收集能匹配这个Matcher链逻辑的描述,从而可以以某种方法将这种匹配逻辑描述出来,在默认实现中是通过文本的形式表达出来的,因而如上图类图结构所示,Description的继承结构是BaseDescription实现了Description接口,而StringDescription则是继承自BaseDescription。
Description接口定义了如下方法,从而可以在Matcher链中收集Matcher的匹配逻辑;貌似其实现没有什么多的可以讲的,唯一可以讲的就是在appendValue()方法中,对不同的值类型会做不同处理,比如对null值,用”null”字符串描述;对String类型,需要对特殊字符做转义;对字符类型,前后加引号;对Short类型,用s表示,并包裹在<>中;对Long类型,用L表示,也包裹在<>中;对Float类型,用F表示,包裹在<>中;对数组类型,遍历数组中的所有内容;对其他类型,将其toString值包裹在<>中;另外,对为什么引入SelfDescribingValueIterator和SelfDescribingValue这两个类是为了重用,这里的重用是通过创建相同的Iterator来实现的,而我自己经常的思维是定义一个接口,以封装不同的点,比如这里是append值的时候,对Object值类型和SelfDescribing类型的处理方式不同,因而可以把这部分逻辑抽取出来;感觉这里的实现为我提供了另外一种思路的参考。对其他的感觉还是直接看代码比较实在一些,继续我的贴代码风格L:
1 public interface Description {
2 Description appendText(String text);
3 Description appendDescriptionOf(SelfDescribing value);
4 Description appendValue(Object value);
5 <T> Description appendValueList(String start, String separator, String end,
6 T values);
7 <T> Description appendValueList(String start, String separator, String end,
8 Iterable<T> values);
9 Description appendList(String start, String separator, String end,
10 Iterable<? extends SelfDescribing> values);
11 }
12 public abstract class BaseDescription implements Description {
13 public Description appendText(String text) {
14 append(text);
15 return this;
16 }
17 public Description appendDescriptionOf(SelfDescribing value) {
18 value.describeTo(this);
19 return this;
20 }
21 public Description appendValue(Object value) {
22 if (value == null) {
23 append("null");
24 } else if (value instanceof String) {
25 toJavaSyntax((String) value);
26 } else if (value instanceof Character) {
27 append('"');
28 toJavaSyntax((Character) value);
29 append('"');
30 } else if (value instanceof Short) {
31 append('<');
32 append(valueOf(value));
33 append("s>");
34 } else if (value instanceof Long) {
35 append('<');
36 append(valueOf(value));
37 append("L>");
38 } else if (value instanceof Float) {
39 append('<');
40 append(valueOf(value));
41 append("F>");
42 } else if (value.getClass().isArray()) {
43 appendValueList("[",", ","]", new ArrayIterator(value));
44 } else {
45 append('<');
46 append(valueOf(value));
47 append('>');
48 }
49 return this;
50 }
51 public <T> Description appendValueList(String start, String separator, String end, T values) {
52 return appendValueList(start, separator, end, Arrays.asList(values));
53 }
54 public <T> Description appendValueList(String start, String separator, String end, Iterable<T> values) {
55 return appendValueList(start, separator, end, values.iterator());
56 }
57 private <T> Description appendValueList(String start, String separator, String end, Iterator<T> values) {
58 return appendList(start, separator, end, new SelfDescribingValueIterator<T>(values));
59 }
60 public Description appendList(String start, String separator, String end, Iterable<? extends SelfDescribing> values) {
61 return appendList(start, separator, end, values.iterator());
62 }
63 private Description appendList(String start, String separator, String end, Iterator<? extends SelfDescribing> i) {
64 boolean separate = false;
65 append(start);
66 while (i.hasNext()) {
67 if (separate) append(separator);
68 appendDescriptionOf(i.next());
69 separate = true;
70 }
71 append(end);
72 return this;
73 }
74 protected void append(String str) {
75 for (int i = 0; i < str.length(); i++) {
76 append(str.charAt(i));
77 }
78 }
79 protected abstract void append(char c);
80 private void toJavaSyntax(String unformatted) {
81 append('"');
82 for (int i = 0; i < unformatted.length(); i++) {
83 toJavaSyntax(unformatted.charAt(i));
84 }
85 append('"');
86 }
87 private void toJavaSyntax(char ch) {
88 switch (ch) {
89 case '"':
90 append("\\\"");
91 break;
92 case '\n':
93 append("\\n");
94 break;
95 case '\r':
96 append("\\r");
97 break;
98 case '\t':
99 append("\\t");
100 break;
101 default:
102 append(ch);
103 }
104 }
105 }
106 public class StringDescription extends BaseDescription {
107 private final Appendable out;
108 public StringDescription() {
109 this(new StringBuilder());
110 }
111 public StringDescription(Appendable out) {
112 this.out = out;
113 }
114 public static String toString(SelfDescribing value) {
115 return new StringDescription().appendDescriptionOf(value).toString();
116 }
117 public static String asString(SelfDescribing selfDescribing) {
118 return toString(selfDescribing);
119 }
120 protected void append(String str) {
121 try {
122 out.append(str);
123 } catch (IOException e) {
124 throw new RuntimeException("Could not write description", e);
125 }
126 }
127 protected void append(char c) {
128 try {
129 out.append(c);
130 } catch (IOException e) {
131 throw new RuntimeException("Could not write description", e);
132 }
133 }
134 public String toString() {
135 return out.toString();
136 }
137 } ConcreteComponent
在hamcrest Matcher的实现中,ConcreteComponent包括5个Matcher:IsAnything、IsEqual、IsSame、IsNull、IsInstanceOf。它们都是一个基本的匹配判断逻辑:IsAnything匹配所有传入的Object;IsEqual匹配actual object和expected object实例相等(equal方法),包括null的情况和object实例是数组的情况;IsSame匹配当前actual object实例和expected object实例是相同的引用;IsNull匹配actual object是为null;IsInstanceOf匹配actual object实例是expected Class的实例。
对于Description的创建,IsAnything会append构建IsAnything时传入的字符串或默认的“ANYTHING”;IsEqual会append当前Matcher保存的Object实例(append逻辑参考BaseDescription中的appendValue()方法);IsSame会append一段字符串:same(${object});IsNull直接append “null”字符串;IsInstanceOf会append一段字符串:an instance of(${className})。
1 public class IsAnything<T> extends BaseMatcher<T> {
2 private final String description;
3 public IsAnything() {
4 this("ANYTHING");
5 }
6 public IsAnything(String description) {
7 this.description = description;
8 }
9 public boolean matches(Object o) {
10 return true;
11 }
12 public void describeTo(Description description) {
13 description.appendText(this.description);
14 }
15 @Factory
16 public static <T> Matcher<T> anything() {
17 return new IsAnything<T>();
18 }
19 @Factory
20 public static <T> Matcher<T> anything(String description) {
21 return new IsAnything<T>(description);
22 }
23 @Factory
24 public static <T> Matcher<T> any(Class<T> type) {
25 return new IsAnything<T>();
26 }
27 }
28 public class IsEqual<T> extends BaseMatcher<T> {
29 private final Object object;
30 public IsEqual(T equalArg) {
31 object = equalArg;
32 }
33 public boolean matches(Object arg) {
34 return areEqual(object, arg);
35 }
36 public void describeTo(Description description) {
37 description.appendValue(object);
38 }
39 private static boolean areEqual(Object o1, Object o2) {
40 if (o1 == null || o2 == null) {
41 return o1 == null && o2 == null;
42 } else if (isArray(o1)) {
43 return isArray(o2) && areArraysEqual(o1, o2);
44 } else {
45 return o1.equals(o2);
46 }
47 }
48 private static boolean areArraysEqual(Object o1, Object o2) {
49 return areArrayLengthsEqual(o1, o2)
50 && areArrayElementsEqual(o1, o2);
51 }
52 private static boolean areArrayLengthsEqual(Object o1, Object o2) {
53 return Array.getLength(o1) == Array.getLength(o2);
54 }
55 private static boolean areArrayElementsEqual(Object o1, Object o2) {
56 for (int i = 0; i < Array.getLength(o1); i++) {
57 if (!areEqual(Array.get(o1, i), Array.get(o2, i))) return false;
58 }
59 return true;
60 }
61 private static boolean isArray(Object o) {
62 return o.getClass().isArray();
63 }
64 @Factory
65 public static <T> Matcher<T> equalTo(T operand) {
66 return new IsEqual<T>(operand);
67 }
68 }
69 public class IsSame<T> extends BaseMatcher<T> {
70 private final T object;
71 public IsSame(T object) {
72 this.object = object;
73 }
74 public boolean matches(Object arg) {
75 return arg == object;
76 }
77 public void describeTo(Description description) {
78 description.appendText("same(") .appendValue(object) .appendText(")");
79 }
80 @Factory
81 public static <T> Matcher<T> sameInstance(T object) {
82 return new IsSame<T>(object);
83 }
84 }
85 public class IsNull<T> extends BaseMatcher<T> {
86 public boolean matches(Object o) {
87 return o == null;
88 }
89 public void describeTo(Description description) {
90 description.appendText("null");
91 }
92 @Factory
93 public static <T> Matcher<T> nullValue() {
94 return new IsNull<T>();
95 }
96 @Factory
97 public static <T> Matcher<T> notNullValue() {
98 return not(IsNull.<T>nullValue());
99 }
100 @Factory
101 public static <T> Matcher<T> nullValue(Class<T> type) {
102 return nullValue();
103 }
104 @Factory
105 public static <T> Matcher<T> notNullValue(Class<T> type) {
106 return notNullValue();
107 }
108 }
109 public class IsInstanceOf extends BaseMatcher<Object> {
110 private final Class<?> theClass;
111 public IsInstanceOf(Class<?> theClass) {
112 this.theClass = theClass;
113 }
114 public boolean matches(Object item) {
115 return theClass.isInstance(item);
116 }
117 public void describeTo(Description description) {
118 description.appendText("an instance of ")
119 .appendText(theClass.getName());
120 }
121 @Factory
122 public static Matcher<Object> instanceOf(Class<?> type) {
123 return new IsInstanceOf(type);
124 }
125 }
Decorator
Decorator名称源于UI中的设计,比如在一个窗口上加入边框、Scrollbar等原件,因而成为装饰。而在现实中,Decorator引申为可以在ConcretComponent的之上加入一些更多功能的类。对Matcher中的Decorator Matcher主要氛围两类:1,加入更多的描述信息,如Is、DescribedAs;2,加入一些逻辑组合,如剩下的其他Matcher,IsNot、AnyOf、AllOf、CombinableMatcher等。
Is这个Matcher没有提供更多的行为,它只是在描述前加入“is ”字符串,从而是错误信息的描述信息更加符合阅读习惯。
1 public class Is<T> extends BaseMatcher<T> {
2 private final Matcher<T> matcher;
3 public Is(Matcher<T> matcher) {
4 this.matcher = matcher;
5 }
6 public boolean matches(Object arg) {
7 return matcher.matches(arg);
8 }
9 public void describeTo(Description description) {
10 description.appendText("is ").appendDescriptionOf(matcher);
11 }
12 @Factory
13 public static <T> Matcher<T> is(Matcher<T> matcher) {
14 return new Is<T>(matcher);
15 }
16 @Factory
17 public static <T> Matcher<T> is(T value) {
18 return is(equalTo(value));
19 }
20 @Factory
21 public static Matcher<Object> is(Class<?> type) {
22 return is(instanceOf(type));
23 }
24 }
DescribedAs这个Matcher也不会增加行为,但是可以提供更加多的描述信息,它提供根据给定不同值替换字符串模板的功能,而替换后的字符串会作为该Matcher新添加的字符串。字符串的模板以”(sequence)”作为占位符。
1 public class DescribedAs<T> extends BaseMatcher<T> {
2 private final String descriptionTemplate;
3 private final Matcher<T> matcher;
4 private final Object[] values;
5 private final static Pattern ARG_PATTERN = Pattern.compile("%([0-9]+)");
6 public DescribedAs(String descriptionTemplate, Matcher<T> matcher, Object[] values) {
7 this.descriptionTemplate = descriptionTemplate;
8 this.matcher = matcher;
9 this.values = values.clone();
10 }
11 public boolean matches(Object o) {
12 return matcher.matches(o);
13 }
14 public void describeTo(Description description) {
15 java.util.regex.Matcher arg = ARG_PATTERN.matcher(descriptionTemplate);
16 int textStart = 0;
17 while (arg.find()) {
18 description.appendText(descriptionTemplate.substring(textStart, arg.start()));
19 int argIndex = Integer.parseInt(arg.group(1));
20 description.appendValue(values[argIndex]);
21 textStart = arg.end();
22 }
23 if (textStart < descriptionTemplate.length()) {
24 description.appendText(descriptionTemplate.substring(textStart));
25 }
26 }
27 @Factory
28 public static <T> Matcher<T> describedAs(String description, Matcher<T> matcher, Object values) {
29 return new DescribedAs<T>(description, matcher, values);
30 }
31 } IsNot这个Matcher提供“非逻辑”,并在描述前加”not “字符串。
1 public class IsNot<T> extends BaseMatcher<T> {
2 private final Matcher<T> matcher;
3 public IsNot(Matcher<T> matcher) {
4 this.matcher = matcher;
5 }
6 public boolean matches(Object arg) {
7 return !matcher.matches(arg);
8 }
9 public void describeTo(Description description) {
10 description.appendText("not ").appendDescriptionOf(matcher);
11 }
12 @Factory
13 public static <T> Matcher<T> not(Matcher<T> matcher) {
14 return new IsNot<T>(matcher);
15 }
16 @Factory
17 public static <T> Matcher<T> not(T value) {
18 return not(equalTo(value));
19 }
20 }
AnyOf这个Matcher提供“或逻辑”,所有内部Matcher之间的描述用“ or ”隔开,并在每个Matcher前后添加括号:(matcher1Description) or (matcher2Description)。如果代码中的或逻辑一样,这个或也是支持短路的,即当前一个Matcher已经成功,后面的所有Matcher都不会执行。
1 public class AnyOf<T> extends BaseMatcher<T> {
2 private final Iterable<Matcher<? extends T>> matchers;
3 public AnyOf(Iterable<Matcher<? extends T>> matchers) {
4 this.matchers = matchers;
5 }
6 public boolean matches(Object o) {
7 for (Matcher<? extends T> matcher : matchers) {
8 if (matcher.matches(o)) {
9 return true;
10 }
11 }
12 return false;
13 }
14 public void describeTo(Description description) {
15 description.appendList("(", " or ", ")", matchers);
16 }
17 @Factory
18 public static <T> Matcher<T> anyOf(Matcher<? extends T> matchers) {
19 return anyOf(Arrays.asList(matchers));
20 }
21 @Factory
22 public static <T> Matcher<T> anyOf(Iterable<Matcher<? extends T>> matchers) {
23 return new AnyOf<T>(matchers);
24 }
25 } AllOf这个Matcher提供“与逻辑”,所有内部Matcher之间的描述用“ and ”隔开,并在每个Matcher前后添加括号:(matcher1Description) and (matcher2Description)。如果代码中的或逻辑一样,这个或也是支持短路的,即当前一个Matcher已经失败,后面的所有Matcher都不会执行。
1 public class AllOf<T> extends BaseMatcher<T> {
2 private final Iterable<Matcher<? extends T>> matchers;
3 public AllOf(Iterable<Matcher<? extends T>> matchers) {
4 this.matchers = matchers;
5 }
6 public boolean matches(Object o) {
7 for (Matcher<? extends T> matcher : matchers) {
8 if (!matcher.matches(o)) {
9 return false;
10 }
11 }
12 return true;
13 }
14 public void describeTo(Description description) {
15 description.appendList("(", " and ", ")", matchers);
16 }
17 @Factory
18 public static <T> Matcher<T> allOf(Matcher<? extends T> matchers) {
19 return allOf(Arrays.asList(matchers));
20 }
21 @Factory
22 public static <T> Matcher<T> allOf(Iterable<Matcher<? extends T>> matchers) {
23 return new AllOf<T>(matchers);
24 }
25 } CombinableMatcher这个Matcher是对两个Matcher的或逻辑和与逻辑的简化支持,其它逻辑和AnyOf、AllOf相同。它提供了or()和and()两个方法。
1 public class CombinableMatcher<T> extends BaseMatcher<T> {
2 private final Matcher<? extends T> fMatcher;
3 public CombinableMatcher(Matcher<? extends T> matcher) {
4 fMatcher= matcher;
5 }
6 public boolean matches(Object item) {
7 return fMatcher.matches(item);
8 }
9 public void describeTo(Description description) {
10 description.appendDescriptionOf(fMatcher);
11 }
12 public CombinableMatcher<T> and(Matcher<? extends T> matcher) {
13 return new CombinableMatcher<T>(allOf(matcher, fMatcher));
14 }
15 public CombinableMatcher<T> or(Matcher<? extends T> matcher) {
16 return new CombinableMatcher<T>(anyOf(matcher, fMatcher));
17 }
18 }
Factory
为了是代码更加可读,hamcrest中的Matcher都是通过一些静态方法构建,并以静态方式导入到一个类中,从而可以直接使用这个静态方法。但是如果在一个源码文件中要用到很多Matcher,就需要导入多个Matcher,而且使用时还需要了解并记住多个Matcher的名字,这样显然是不方便的,因而hamcrest框架提供了一个叫@Factory的注解,并且提供一个工具,这个工具可以将所有有@Factory注解的方法提取到一个单独的类中(如CoreMatchers),这样,对用户来说,我们只需要导入这个类中的所有静态方法就可以了,而且只需要记住那些比较容易记住的方法即可,而不需要记住他们对应的Matcher类。
JUnit中的Matchers
JUnit也实现了一些自己的Matcher以扩展Matcher的应用。如IsCollectionContaining,判断一个actual collection中是否包含expected item。它同时会在描述前加“a collection containing ”
1 public class IsCollectionContaining<T> extends TypeSafeMatcher<Iterable<T>> {
2 private final Matcher<? extends T> elementMatcher;
3 public IsCollectionContaining(Matcher<? extends T> elementMatcher) {
4 this.elementMatcher = elementMatcher;
5 }
6 @Override
7 public boolean matchesSafely(Iterable<T> collection) {
8 for (T item : collection) {
9 if (elementMatcher.matches(item)){
10 return true;
11 }
12 }
13 return false;
14 }
15 public void describeTo(Description description) {
16 description
17 .appendText("a collection containing ")
18 .appendDescriptionOf(elementMatcher);
19 }
20 @Factory
21 public static <T> Matcher<Iterable<T>> hasItem(Matcher<? extends T> elementMatcher) {
22 return new IsCollectionContaining<T>(elementMatcher);
23 }
24 @Factory
25 public static <T> Matcher<Iterable<T>> hasItem(T element) {
26 return hasItem(equalTo(element));
27 }
28 @Factory
29 public static <T> Matcher<Iterable<T>> hasItems(Matcher<? extends T> elementMatchers) {
30 Collection<Matcher<? extends Iterable<T>>> all
31 = new ArrayList<Matcher<? extends Iterable<T>>>(elementMatchers.length);
32 for (Matcher<? extends T> elementMatcher : elementMatchers) {
33 all.add(hasItem(elementMatcher));
34 }
35 return allOf(all);
36 }
37 @Factory
38 public static <T> Matcher<Iterable<T>> hasItems(T elements) {
39 Collection<Matcher<? extends Iterable<T>>> all
40 = new ArrayList<Matcher<? extends Iterable<T>>>(elements.length);
41 for (T element : elements) {
42 all.add(hasItem(element));
43 }
44 return allOf(all);
45 }
46 } StringContains判断actual string是否包含expected string,若不是,其描述信息为“a string containing ”。
1 public abstract class SubstringMatcher extends TypeSafeMatcher<String> {
2 protected final String substring;
3 protected SubstringMatcher(final String substring) {
4 this.substring = substring;
5 }
6 @Override
7 public boolean matchesSafely(String item) {
8 return evalSubstringOf(item);
9 }
10 public void describeTo(Description description) {
11 description.appendText("a string ")
12 .appendText(relationship())
13 .appendText(" ")
14 .appendValue(substring);
15 }
16 protected abstract boolean evalSubstringOf(String string);
17 protected abstract String relationship();
18 }
19 public class StringContains extends SubstringMatcher {
20 public StringContains(String substring) {
21 super(substring);
22 }
23 @Override
24 protected boolean evalSubstringOf(String s) {
25 return s.indexOf(substring) >= 0;
26 }
27 @Override
28 protected String relationship() {
29 return "containing";
30 }
31 @Factory
32 public static Matcher<String> containsString(String substring) {
33 return new StringContains(substring);
34 }
35 }
Each类提供each()方法,实现actual collection中所有item匹配expected matcher的逻辑,若不匹配,则加入“each ”描述字符串。这里的实现逻辑比较绕,它首先找一个actual collection中找匹配非expected matcher的项,如果找到,表明匹配不成功,否则,匹配成功,因而对这个结果再取非,即是actual collection中的所有item都匹配expected matcher。
1 public class Each {
2 public static <T> Matcher<Iterable<T>> each(final Matcher<T> individual) {
3 final Matcher<Iterable<T>> allItemsAre = not(hasItem(not(individual)));
4 return new BaseMatcher<Iterable<T>>() {
5 public boolean matches(Object item) {
6 return allItemsAre.matches(item);
7 }
8
9 public void describeTo(Description description) {
10 description.appendText("each ");
11 individual.describeTo(description);
12 }
13 };
14 }
15 }
Assume
Assume类提供Matcher支持:assumeThat()方法,并且提供几个常用方法的简化,如assumeNotNull(),assumeNoException()。若Assume中的方法调用失败,会抛出AssumeViolatedException,但是这并不认为测试方法失败,测试方法只是在异常抛出点停止执行,然后触发testAssumptionFailure事件,JUnit默认处理不处理这个事件,因而即使这个异常抛出,JUnit还是认为这个测试方法通过了。
1 public class Assume {
2 public static void assumeTrue(boolean b) {
3 assumeThat(b, is(true));
4 }
5 public static void assumeNotNull(Object objects) {
6 assumeThat(asList(objects), Each.each(notNullValue()));
7 }
8 public static <T> void assumeThat(T actual, Matcher<T> matcher) {
9 if (!matcher.matches(actual))
10 throw new AssumptionViolatedException(actual, matcher);
11 }
12 public static void assumeNoException(Throwable t) {
13 assumeThat(t, nullValue());
14 }
15 }