• junit4X系列--Assert与Hamcrest


    原文出处:http://www.blogjava.net/DLevin/archive/2012/05/12/377960.html。感谢作者无私分享

    到目前,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(" Expected: ");
    10        description.appendDescriptionOf(matcher);
    11        description.appendText("      got: ");
    12        description.appendValue(actual);
    13        description.appendText(" ");
    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 ' ':
     93                 append("\n");
     94                 break;
     95             case ' ':
     96                 append("\r");
     97                 break;
     98             case ' ':
     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 }

     

  • 相关阅读:
    Laravel 如何在blade文件中使用Vue组件
    历史上的今天mysql数据库包含详情分类以及图片
    【问题】多重继承时,super函数只初始化继承的第一个类,不初始化第二个类。
    pretty-errors:美化python异常输出以使其清晰易读
    python 安装pyinstaller
    python制作ico图标
    Unofficial Windows Binaries for Python Extension Packages
    【转载】wav文件格式分析与详解
    C语言结构体定义位域,从bit0开始,依次到最高bit位
    IP切换脚本
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5232869.html
Copyright © 2020-2023  润新知