原文出处: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 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,

               (String) actual);

     } else

        failNotEquals(message, expected, actual);

 }

 private static boolean isEquals(Object expected, Object actual) {

     return expected.equals(actual);

 }

 static private void failNotEquals(String message, Object expected,

        Object actual) {

     fail(format(message, expected, actual));

 }

 static String format(String message, Object expected, Object actual) {

     String formatted= "";

     if (message != null && !message.equals(""))

        formatted= message + " ";

     String expectedString= String.valueOf(expected);

     String actualString= String.valueOf(actual);

     if (expectedString.equals(actualString))

        return formatted + "expected: "

               + formatClassAndValue(expected, expectedString)

               + " but was: " + formatClassAndValue(actual, actualString);

     else

        return formatted + "expected:<" + expectedString + "> but was:<"

               + actualString + ">";

 }

 private static String formatClassAndValue(Object value, String valueString) {

     String className= value == null ? "null" : value.getClass().getName();

     return className + "<" + valueString + ">";

 }

 //对于其他方法,只是对上面方法的包装

 static public void assertEquals(Object expected, Object actual) {

     assertEquals(null, expected, actual);

 }

 static public void assertEquals(long expected, long actual) {

     assertEquals(null, expected, actual);

 }

 static public void assertEquals(String message, long expected, long actual) {

     assertEquals(message, (Long) expected, (Long) actual);

 }

 //对于double、float类型有点特殊,因为由于浮点的精度问题,有些时候我们允许测试逻辑返回的结果和预计有差别,这种情况有delta表达,而且double和float都有一些特殊值,因而不能直接用“==”来比较,而需要用Double.compare()方法比较,这个方法对都是Double.NaN放回相等。

 static public void assertEquals(String message, double expected,

        double actual, double delta) {

 )

        return;

     if (!(Math.abs(expected - actual) <= delta))

        failNotEquals(message, new Double(expected), new Double(actual));

 }

 static public void assertEquals(double expected, double actual, double delta) {

     assertEquals(null, expected, actual, delta);

 }

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 }

 //对于其他方法,只是对上面方法的包装

 public static void assertArrayEquals(Object[] expecteds, Object[] actuals) {

     assertArrayEquals(null, expecteds, actuals);

 }

 public static void assertArrayEquals(String message, byte[] expecteds,

        byte[] actuals) throws ArrayComparisonFailure {

     internalArrayEquals(message, expecteds, actuals);

 }

 public static void assertArrayEquals(byte[] expecteds, byte[] actuals) {

     assertArrayEquals(null, expecteds, actuals);

 }

 public static void assertArrayEquals(String message, char[] expecteds,

        char[] actuals) throws ArrayComparisonFailure {

     internalArrayEquals(message, expecteds, actuals);

 }

 public static void assertArrayEquals(char[] expecteds, char[] actuals) {

     assertArrayEquals(null, expecteds, actuals);

 }

 public static void assertArrayEquals(String message, short[] expecteds,

        short[] actuals) throws ArrayComparisonFailure {

     internalArrayEquals(message, expecteds, actuals);

 }

 public static void assertArrayEquals(short[] expecteds, short[] actuals) {

     assertArrayEquals(null, expecteds, actuals);

 }

 public static void assertArrayEquals(String message, int[] expecteds,

        int[] actuals) throws ArrayComparisonFailure {

     internalArrayEquals(message, expecteds, actuals);

 }

 public static void assertArrayEquals(int[] expecteds, int[] actuals) {

     assertArrayEquals(null, expecteds, actuals);

 }

 public static void assertArrayEquals(String message, long[] expecteds,

        long[] actuals) throws ArrayComparisonFailure {

     internalArrayEquals(message, expecteds, actuals);

 }

 public static void assertArrayEquals(long[] expecteds, long[] actuals) {

     assertArrayEquals(null, expecteds, actuals);

 }

 //对double和float逻辑有点特殊,因为他们的比较需要提供delta值,因而使用InexactComparisonCriteria,不过除了考虑delta因素,其他比较逻辑相同

 public static void assertArrayEquals(String message, double[] expecteds,

        double[] actuals, double delta) throws ArrayComparisonFailure {

     new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals);

 }

 public static void assertArrayEquals(double[] expecteds, double[] actuals, double delta) {

     assertArrayEquals(null, expecteds, actuals, delta);

 }

 public static void assertArrayEquals(String message, float[] expecteds,

        float[] actuals, float delta) throws ArrayComparisonFailure {

     new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals);

 }

 public static void assertArrayEquals(float[] expecteds, float[] actuals, float delta) {

     assertArrayEquals(null, expecteds, actuals, delta);

 }

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)

        formatted= message + " ";

     fail(formatted + "expected same:<" + expected + "> was not:<" + actual

            + ">");

 }

 static public void assertSame(Object expected, Object actual) {

     assertSame(null, expected, actual);

 }

 static public void assertNotSame(String message, Object unexpected,

        Object actual) {

     if (unexpected == actual)

        failSame(message);

 }

 static public void assertNotSame(Object unexpected, Object actual) {

     assertNotSame(null, unexpected, actual);

 }

 static private void failSame(String message) {

     String formatted= "";

     if (message != null)

        formatted= message + " ";

     fail(formatted + "expected not same");

 }

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 }

 static public void assertNull(Object object) {

     assertNull(null, object);

 }

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);

 }

 static public void assertFalse(boolean condition) {

     assertFalse(null, condition);

 }

 static public void fail(String message) {

     if (message == null)

        throw new AssertionError();

     throw new AssertionError(message);

 }

 static public void fail() {

     fail(null);

 }

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: ");

        description.appendDescriptionOf(matcher);

        description.appendText("\n     got: ");

        description.appendValue(actual);

        description.appendText("\n");

        throw new java.lang.AssertionError(description.toString());

     }

 }

使用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_() {

         // See Matcher interface for an explanation of this method.

     }

     @Override

     public String toString() {

         return StringDescription.toString(this);

     }

 }

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) {

; 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('"');

; 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");

                 break;

             default:

                 append(ch);

         }

     }

 }

 public class StringDescription extends BaseDescription {

     private final Appendable out;

     public StringDescription() {

         this(new StringBuilder());

     }

     public StringDescription(Appendable out) {

         this.out = out;

     }

     public static String toString(SelfDescribing value) {

     return new StringDescription().appendDescriptionOf(value).toString();

     }

     public static String asString(SelfDescribing selfDescribing) {

         return toString(selfDescribing);

     }

     protected void append(String str) {

         try {

             out.append(str);

         } catch (IOException e) {

             throw new RuntimeException("Could not write description", e);

         }

     }

     protected void append(char c) {

         try {

             out.append(c);

         } catch (IOException e) {

             throw new RuntimeException("Could not write description", e);

         }

     }

     public String toString() {

         return out.toString();

     }

 }

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) {

; 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     }

     @Factory

     public static <T> Matcher<T> nullValue(Class<T> type) {

         return nullValue();

     }

     @Factory

     public static <T> Matcher<T> notNullValue(Class<T> type) {

         return notNullValue();

     }

 }

 public class IsInstanceOf extends BaseMatcher<Object> {

     private final Class<?> theClass;

     public IsInstanceOf(Class<?> theClass) {

         this.theClass = theClass;

     }

     public boolean matches(Object item) {

         return theClass.isInstance(item);

     }

     public void describeTo(Description description) {

         description.appendText("an instance of ")

                 .appendText(theClass.getName());

     }

     @Factory

     public static Matcher<Object> instanceOf(Class<?> type) {

         return new IsInstanceOf(type);

     }

 }

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) {

         description.appendText("is ").appendDescriptionOf(matcher);

     }

     @Factory

     public static <T> Matcher<T> is(Matcher<T> matcher) {

         return new Is<T>(matcher);

     }

     @Factory

     public static <T> Matcher<T> is(T value) {

         return is(equalTo(value));

     }

     @Factory

     public static Matcher<Object> is(Class<?> type) {

         return is(instanceOf(type));

     }

 }

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();

     }

     public boolean matches(Object o) {

         return matcher.matches(o);

     }

     public void describeTo(Description description) {

         java.util.regex.Matcher arg = ARG_PATTERN.matcher(descriptionTemplate);

 ;

         while (arg.find()) {

             description.appendText(descriptionTemplate.substring(textStart, arg.start()));

 ));

             description.appendValue(values[argIndex]);

             textStart = arg.end();

         }

         if (textStart < descriptionTemplate.length()) {

             description.appendText(descriptionTemplate.substring(textStart));

         }

     }

     @Factory

     public static <T> Matcher<T> describedAs(String description, Matcher<T> matcher, Object values) {

         return new DescribedAs<T>(description, matcher, values);

     }

 }

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) {

         description.appendText("not ").appendDescriptionOf(matcher);

     }

     @Factory

     public static <T> Matcher<T> not(Matcher<T> matcher) {

         return new IsNot<T>(matcher);

     }

     @Factory

     public static <T> Matcher<T> not(T value) {

         return not(equalTo(value));

     }

 }

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;

             }

         }

         return false;

     }

     public void describeTo(Description description) {

        description.appendList("(", " or ", ")", matchers);

     }

     @Factory

     public static <T> Matcher<T> anyOf(Matcher<? extends T> matchers) {

         return anyOf(Arrays.asList(matchers));

     }

     @Factory

     public static <T> Matcher<T> anyOf(Iterable<Matcher<? extends T>> matchers) {

         return new AnyOf<T>(matchers);

     }

 }

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;

             }

         }

         return true;

     }

     public void describeTo(Description description) {

         description.appendList("(", " and ", ")", matchers);

     }

     @Factory

     public static <T> Matcher<T> allOf(Matcher<? extends T> matchers) {

         return allOf(Arrays.asList(matchers));

     }

     @Factory

     public static <T> Matcher<T> allOf(Iterable<Matcher<? extends T>> matchers) {

         return new AllOf<T>(matchers);

     }

 }

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) {

        description.appendDescriptionOf(fMatcher);

     }

     public CombinableMatcher<T> and(Matcher<? extends T> matcher) {

        return new CombinableMatcher<T>(allOf(matcher, fMatcher));

     }

     public CombinableMatcher<T> or(Matcher<? extends T> matcher) {

        return new CombinableMatcher<T>(anyOf(matcher, fMatcher));

     }

 }

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)){

                 return true;

             }

         }

         return false;

     }

     public void describeTo(Description description) {

         description

         .appendText("a collection containing ")

         .appendDescriptionOf(elementMatcher);

     }

     @Factory

     public static <T> Matcher<Iterable<T>> hasItem(Matcher<? extends T> elementMatcher) {

         return new IsCollectionContaining<T>(elementMatcher);

     }

     @Factory

     public static <T> Matcher<Iterable<T>> hasItem(T element) {

         return hasItem(equalTo(element));

     }

     @Factory

     public static <T> Matcher<Iterable<T>> hasItems(Matcher<? extends T> elementMatchers) {

         Collection<Matcher<? extends Iterable<T>>> all

                 = new ArrayList<Matcher<? extends Iterable<T>>>(elementMatchers.length);

         for (Matcher<? extends T> elementMatcher : elementMatchers) {

             all.add(hasItem(elementMatcher));

         }

         return allOf(all);

     }

     @Factory

     public static <T> Matcher<Iterable<T>> hasItems(T elements) {

         Collection<Matcher<? extends Iterable<T>>> all

                 = new ArrayList<Matcher<? extends Iterable<T>>>(elements.length);

         for (T element : elements) {

             all.add(hasItem(element));

         }

         return allOf(all);

     }

 }

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     }

     public void describeTo(Description description) {

         description.appendText("a string ")

                 .appendText(relationship())

                 .appendText(" ")

                 .appendValue(substring);

     }

     protected abstract boolean evalSubstringOf(String string);

     protected abstract String relationship();

 }

 public class StringContains extends SubstringMatcher {

     public StringContains(String substring) {

         super(substring);

     }

     @Override

     protected boolean evalSubstringOf(String s) {

 ;

     }

     @Override

     protected String relationship() {

         return "containing";

     }

     @Factory

     public static Matcher<String> containsString(String substring) {

         return new StringContains(substring);

     }

 }

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) {

               description.appendText("each ");

               individual.describeTo(description);

            }

        };

     }

 }

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))

            throw new AssumptionViolatedException(actual, matcher);

     }

     public static void assumeNoException(Throwable t) {

        assumeThat(t, nullValue());

     }

 }

junit4X系列--Assert与Hamcrest的更多相关文章

  1. Junit4X系列--hamcrest的使用

    OK,在前面的一系列博客里面,我整理过了Assert类下面常用的断言方法,比如assertEquals等等,但是org.junit.Assert类下还有一个方法也用来断言,而且更加强大.这就是我们这里 ...

  2. junit4X系列--Runner解析

    前面我整理了junit38系列的源码,那junit4X核心代码也基本类似.这里我先转载一些关于junit4X源码解析的好文章.感谢原作者的分享.原文地址:http://www.blogjava.net ...

  3. junit4X系列--Exception

    原文出处:http://www.blogjava.net/DLevin/archive/2012/11/02/390684.html.感谢作者的无私分享. 说来惭愧,虽然之前已经看过JUnit的源码了 ...

  4. junit4X系列--Rule

    原文出处:http://www.blogjava.net/DLevin/archive/2012/05/12/377955.html.感谢作者的无私分享. 初次用文字的方式记录读源码的过程,不知道怎么 ...

  5. junit4X系列源码--总体介绍

    原文出处:http://www.cnblogs.com/caoyuanzhanlang/p/3530267.html.感谢作者的无私分享. Junit是一个可编写重复测试的简单框架,是基于Xunit架 ...

  6. junit4X系列--Builder、Request与JUnitCore

    原文出处:http://www.blogjava.net/DLevin/archive/2012/05/12/377957.html.感谢作者的无私分享. 初次用文字的方式记录读源码的过程,不知道怎么 ...

  7. junit4X系列源码--Junit4 Runner以及test case执行顺序和源代码理解

    原文出处:http://www.cnblogs.com/caoyuanzhanlang/p/3534846.html.感谢作者的无私分享. 前一篇文章我们总体介绍了Junit4的用法以及一些简单的测试 ...

  8. junit4X系列--Statement

    原文出处:http://www.blogjava.net/DLevin/archive/2012/05/11/377954.html.感谢作者的无私分享. 初次用文字的方式记录读源码的过程,不知道怎么 ...

  9. Junit 断言 assertThat Hamcrest匹配器

    junit断言总结本文参考了http://blog.csdn.net/wangpeng047/article/details/9628449一 junit断言1.JUnit框架用一组assert方法封 ...

随机推荐

  1. linux上配置bochs,搭建基于X86架构操作系统的开发环境

    学习操作系统最好的方法就是自己编写新的操作系统,或者修改已有的操作系统.但是如果在真机上完成这个过程,调试会成为一个很大的问题.利用虚拟机来完成,可以使调试过程变得简单,而且能节约很多开关机的时间. ...

  2. arm swi 软中断 一例

    原文在CU,挪过来了. 1. 目标 本文单纯验证swi指令相关功能 2. 环境 vmware + redhat 9 + arm-elf-gcc 2.95 + skyeye-1.2.6_rc1(模拟s3 ...

  3. python3之迭代器&生成器

    1.迭代器 迭代是Python最强大的功能之一,是访问集合元素的一种方式.. 迭代器是一个可以记住遍历的位置的对象. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不 ...

  4. java多线程之守护线程以及Join方法

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.守护线程概述及示例 守护线程就是为其它线程提供"守护"作用,说白了就是为其它线程服务的,比如GC线程. java程序中线程分 ...

  5. IntelliJ IDEA 使用技巧

    本着工欲善其事必先利其器的精神,闷头写代码之外花点时间研究一下自己用的 IDE,其带来的效率提升非常可观. 高效定位代码 无处不在的跳转 项目之间跳转 下一个 ctrl + alt + ] 上一个 c ...

  6. 用clipboard.js实现纯JS复制文本到剪切板

    以前很多人都是用ZeroClipboard.js来实现网页复制内容,火端也是用它.ZeroClipboard是利用flash来实现的,ZeroClipboard兼容性很好,但是由于现在越来越多的浏览器 ...

  7. 学习笔记 - 兼容ie的透明度问题

    .opacity{font-size: 14px;-khtml-opacity:0.6;-moz-opacity:0.6;filter:alpha(opacity=60);filter:"a ...

  8. hackerrank Ticket

    传送门 题意:n个人排队买票,要把他们拆成k条队到k个窗口买,可以有队伍为空,每条队的顺序保持拆之前的顺序.如果某人和他前一个人买的票相同,就可以打八折,求最小花费. 题解:拆成k条队意味着只有[n- ...

  9. BZOJ 2219: 数论之神

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2219 N次剩余+CRT... 就是各种奇怪的分类讨论.. #include<cstrin ...

  10. 基于Echarts4.0实现旭日图

    昨天Echarts4.0正式发布,随着4.0而来的是一系列的更新,挑几个主要的简单说明: 1.展示方面通过增量渲染技术(4.0+)ECharts 能够展现千万级的数据量 2.针对移动端优化,移动端小屏 ...