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 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,这里不再展开。
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类。
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>。
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值的判断需要自己提供消息,不然不会打印具体消息。
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. 直接断言成功或失败。
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的详细信息见下一小节。
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()方法。
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:
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})。
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 ”字符串,从而是错误信息的描述信息更加符合阅读习惯。
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)”作为占位符。
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 “字符串。
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都不会执行。
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都不会执行。
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()两个方法。
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 ”
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 ”。
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。
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还是认为这个测试方法通过了。
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的更多相关文章
- Junit4X系列--hamcrest的使用
OK,在前面的一系列博客里面,我整理过了Assert类下面常用的断言方法,比如assertEquals等等,但是org.junit.Assert类下还有一个方法也用来断言,而且更加强大.这就是我们这里 ...
- junit4X系列--Runner解析
前面我整理了junit38系列的源码,那junit4X核心代码也基本类似.这里我先转载一些关于junit4X源码解析的好文章.感谢原作者的分享.原文地址:http://www.blogjava.net ...
- junit4X系列--Exception
原文出处:http://www.blogjava.net/DLevin/archive/2012/11/02/390684.html.感谢作者的无私分享. 说来惭愧,虽然之前已经看过JUnit的源码了 ...
- junit4X系列--Rule
原文出处:http://www.blogjava.net/DLevin/archive/2012/05/12/377955.html.感谢作者的无私分享. 初次用文字的方式记录读源码的过程,不知道怎么 ...
- junit4X系列源码--总体介绍
原文出处:http://www.cnblogs.com/caoyuanzhanlang/p/3530267.html.感谢作者的无私分享. Junit是一个可编写重复测试的简单框架,是基于Xunit架 ...
- junit4X系列--Builder、Request与JUnitCore
原文出处:http://www.blogjava.net/DLevin/archive/2012/05/12/377957.html.感谢作者的无私分享. 初次用文字的方式记录读源码的过程,不知道怎么 ...
- junit4X系列源码--Junit4 Runner以及test case执行顺序和源代码理解
原文出处:http://www.cnblogs.com/caoyuanzhanlang/p/3534846.html.感谢作者的无私分享. 前一篇文章我们总体介绍了Junit4的用法以及一些简单的测试 ...
- junit4X系列--Statement
原文出处:http://www.blogjava.net/DLevin/archive/2012/05/11/377954.html.感谢作者的无私分享. 初次用文字的方式记录读源码的过程,不知道怎么 ...
- Junit 断言 assertThat Hamcrest匹配器
junit断言总结本文参考了http://blog.csdn.net/wangpeng047/article/details/9628449一 junit断言1.JUnit框架用一组assert方法封 ...
随机推荐
- linux上配置bochs,搭建基于X86架构操作系统的开发环境
学习操作系统最好的方法就是自己编写新的操作系统,或者修改已有的操作系统.但是如果在真机上完成这个过程,调试会成为一个很大的问题.利用虚拟机来完成,可以使调试过程变得简单,而且能节约很多开关机的时间. ...
- arm swi 软中断 一例
原文在CU,挪过来了. 1. 目标 本文单纯验证swi指令相关功能 2. 环境 vmware + redhat 9 + arm-elf-gcc 2.95 + skyeye-1.2.6_rc1(模拟s3 ...
- python3之迭代器&生成器
1.迭代器 迭代是Python最强大的功能之一,是访问集合元素的一种方式.. 迭代器是一个可以记住遍历的位置的对象. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不 ...
- java多线程之守护线程以及Join方法
版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.守护线程概述及示例 守护线程就是为其它线程提供"守护"作用,说白了就是为其它线程服务的,比如GC线程. java程序中线程分 ...
- IntelliJ IDEA 使用技巧
本着工欲善其事必先利其器的精神,闷头写代码之外花点时间研究一下自己用的 IDE,其带来的效率提升非常可观. 高效定位代码 无处不在的跳转 项目之间跳转 下一个 ctrl + alt + ] 上一个 c ...
- 用clipboard.js实现纯JS复制文本到剪切板
以前很多人都是用ZeroClipboard.js来实现网页复制内容,火端也是用它.ZeroClipboard是利用flash来实现的,ZeroClipboard兼容性很好,但是由于现在越来越多的浏览器 ...
- 学习笔记 - 兼容ie的透明度问题
.opacity{font-size: 14px;-khtml-opacity:0.6;-moz-opacity:0.6;filter:alpha(opacity=60);filter:"a ...
- hackerrank Ticket
传送门 题意:n个人排队买票,要把他们拆成k条队到k个窗口买,可以有队伍为空,每条队的顺序保持拆之前的顺序.如果某人和他前一个人买的票相同,就可以打八折,求最小花费. 题解:拆成k条队意味着只有[n- ...
- BZOJ 2219: 数论之神
题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2219 N次剩余+CRT... 就是各种奇怪的分类讨论.. #include<cstrin ...
- 基于Echarts4.0实现旭日图
昨天Echarts4.0正式发布,随着4.0而来的是一系列的更新,挑几个主要的简单说明: 1.展示方面通过增量渲染技术(4.0+)ECharts 能够展现千万级的数据量 2.针对移动端优化,移动端小屏 ...