深入JUnit源码之Assert与Hamcrest

简介:
初次用文字的方式记录读源码的过程,不知道怎么写,感觉有点贴代码的嫌疑。不过中间还是加入了一些自己的理解和心得,希望以后能够慢慢的改进,感兴趣的童鞋凑合着看吧,感觉JUnit这个框架还是值得看的,里面有许多不错的设计思想在,更何况它是Kent Beck和Erich Gamma这样的大师写的。。。。。

深入JUnit源码之AssertHamcrest

到目前,JUnit4所有的核心源码都已经讲解过了,最后剩下的就是为了兼容性而引入的和JUnit3相关的代码以及Assert中的代码。本节将关注于Assert代码。在JUnit4中,对Assert还引入了hamcrest框架的支持,以使断言可读性更好,并且也具有了一定的扩展性。因而本文还会对hamcrest框架做一个简单的介绍。

普通Assert实现

Assert类是JUnit提供的一些断言方法,以供测试方法判断测试逻辑是否符合预期。它主要提供六类方法:

1. 判断对象是否相等。若不相等,一般抛出:“${message} expected: <${expectedToString}> but was: <${actualToString}>”作为error messageAssertionErrorException。如果expectedToString的值和actualToString的值相等,则error message变为:“${message}: expected: ${expectedClassName}<${expectedToString}> but was: ${actualClassName}<${actualToString}>”。

expectedactual都是String类型时,ComparisonFailure还会找出是前后相同的串,并用[Different String]标明那些不相同的字符串,也就是expectedToStringactualToString的格式将会变成:…${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. 判断数组是否相等(支持多维数组)。如果因为expectedsactuals有一个为null而不等,抛出的AssertionError消息为:${message}: expected(actual) array was null。如果因为expectedsactuals的长度不等,跑粗的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. 判断值是否为nullnull值的判断需要自己提供消息,不然不会打印具体消息。

 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框架的assertThatassertThat()方法的引入是JUnithamcrestMatcher的支持,其中matcher为一个expected值的matcher。在assertThat()中,判断matcher是否可以match传入的actual,若否,则打印出错信息。关于hamcrestMatcher的详细信息见下一小节。

 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意义。以我现在的理解,感觉引入hamcrestMatcher有以下几点好处:

1.       提供了统一的编程接口,不管什么样验证逻辑,都可以使用assertThat()这个方法实现,只需要提供不同的Matcher即可。

2.       增加了可扩展性和重用性,因为Matcher可以用户自己定义,那么就相当于把匹配逻辑这一变量封装在了一个类中,这个类可以在不同的场景中替换,即可扩展性;而对一个类,又可以用在相似的场景中,即重用性。让我想起了一句关于面向对象设计的话:哪里变化封装哪里。

3.       增加了提示信息的可读性,这个完全取决于实现,只是Matcher提供了这样一种可能性,因为我们可以在自己的Matcher中自定义提示信息。

4.       增加了代码的可读性,因为Matcher可以以一种更接近自然语言的方式描述要实现的功能,如equalTois等。

不知道是否还有其他更加好或者说更根本的理由L。我一如既往的喜欢先画一个类结构图(hamcrest中的Matchers)。


线条太多了,图画的有点乱,之所以把那些所有对Matcher有引用的Matcher的引用关系画出来是为了表达hamcrestMatcher的实现事实上用了Decorator设计模式。其中BaseMatcher下一层所有的MatcherIsAnythingIsInstanceOfIsEqualIsSameIsNull)是ConcreteComponent,而再下一层所有MatcherIsIsNotDescribedAsCombinableMatcher AnyOfAllOf)都是Decorator。事实上,Decorator Matcher可以引用其他的Decorator Matcher,因而他们可以串在一起,在自己的类中实现自己的逻辑,而把其他逻辑交给其他Matcher,实现类似Chain Of Responsibility模式。模式看多了,越来越感觉很多模式之间都是想通的。

MatcherBaseMatcher

Matcherhamcrest框架的核心,其所有的实现都是围绕这个Matcher展开的。Matcher的主要功能是传入一个类实例,以判断该实例是否能和当前Matcher所定义的逻辑匹配,当出现不匹配的时,Matcher需要能描述能匹配自己的逻辑。因而Matcher中主要有两个方法,其中一个是match()方法,另一个则是describeTo()方法,将自己能匹配的逻辑描述到传入的Description中。hamcrest中规定所有的Matcher都要继承自BaseMatcher,从而在以后的版本变化中可以往Matcher中添加更多的接口,而不需要担心对之前版本的兼容性实现。为了强制这种规定,hamcrest设计者在Matcher接口中添加了_dont_implement_matcher___instea d_base_matcher()方法,这样当有用户想直接实现Matcher时,就需要实现这个方法,根据方法名他就可以得到警告,如果他忽略这个警告而直接在他自己的Matcher中实现了该方法,那么后果就需要由他自己承担。事实上这个设计的想法可以在一些框架实现中做一些参考。对目前hamcrestBaseMatcher的实现比较简单,只是实现了上述的方法和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值包裹在<>中;另外,对为什么引入SelfDescribingValueIteratorSelfDescribingValue这两个类是为了重用,这里的重用是通过创建相同的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包括5MatcherIsAnythingIsEqualIsSameIsNullIsInstanceOf。它们都是一个基本的匹配判断逻辑:IsAnything匹配所有传入的ObjectIsEqual匹配actual objectexpected object实例相等(equal方法),包括null的情况和object实例是数组的情况;IsSame匹配当前actual object实例和expected object实例是相同的引用;IsNull匹配actual object是为nullIsInstanceOf匹配actual object实例是expected Class的实例。

对于Description的创建,IsAnythingappend构建IsAnything时传入的字符串或默认的“ANYTHING”;IsEqualappend当前Matcher保存的Object实例(append逻辑参考BaseDescription中的appendValue()方法);IsSameappend一段字符串:same(${object})IsNull直接append null”字符串;IsInstanceOfappend一段字符串: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,加入更多的描述信息,如IsDescribedAs2,加入一些逻辑组合,如剩下的其他MatcherIsNotAnyOfAllOfCombinableMatcher等。

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的或逻辑和与逻辑的简化支持,其它逻辑和AnyOfAllOf相同。它提供了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 }

 


相关文章
JUnit-4.12使用报java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing错误
JUnit-4.12使用报java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing错误
|
Java 测试技术 Android开发
|
测试技术
关于Junit源码的一些探索
关于Junit源码的一些探讨
1362 0
|
Java Android开发 Linux
|
设计模式 前端开发
|
4月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架