初次用文字的方式記錄讀源碼的過程,不知道怎麼寫,感覺有點貼代碼的嫌疑。不過中間還是加入了一些自己的了解和心得,希望以後能夠慢慢的改進,感興趣的童鞋湊合着看吧,感覺junit這個架構還是值得看的,裡面有許多不錯的設計思想在,更何況它是kent beck和erich gamma這樣的大師寫的。。。。。
到目前,junit4所有的核心源碼都已經講解過了,最後剩下的就是為了相容性而引入的和junit3相關的代碼以及assert中的代碼。本節将關注于assert代碼。在junit4中,對assert還引入了hamcrest架構的支援,以使斷言可讀性更好,并且也具有了一定的擴充性。因而本文還會對hamcrest架構做一個簡單的介紹。
assert類是junit提供的一些斷言方法,以供測試方法判斷測試邏輯是否符合預期。它主要提供六類方法:
1. 判斷對象是否相等。若不相等,一般抛出:“${message} expected: <${expectedtostring}> but was: <${actualtostring}>”作為error message的assertionerror的exception。如果expectedtostring的值和actualtostring的值相等,則error message變為:“${message}: expected: ${expectedclassname}<${expectedtostring}> but was: ${actualclassname}<${actualtostring}>”。
當expected和actual都是string類型時,comparisonfailure還會找出是前後相同的串,并用[different string]标明那些不相同的字元串,也就是expectedtostring和actualtostring的格式将會變成:…${samestring}[${differentstring}]${samestring}…。其中“…”隻會在相同的字元串太長的情況下才會出現,這個長度标準目前(junit4.10)是20個字元。具體實作參考comparisonfailure類,它繼承自assertionerror,這裡不再展開。
1 static public void assertequals(string message, object expected,
2 object actual) {
3 if (expected == null && actual == null)
4 return;
5 if (expected != null && isequals(expected, actual))
6 return;
7 else if (expected instanceof string && actual instanceof string) {
8 string cleanmessage= message == null ? "" : message;
9 throw new comparisonfailure(cleanmessage, (string) expected,
10 (string) actual);
11 } else
12 failnotequals(message, expected, actual);
13 }
14 private static boolean isequals(object expected, object actual) {
15 return expected.equals(actual);
16 }
17 static private void failnotequals(string message, object expected,
18 object actual) {
19 fail(format(message, expected, actual));
20 }
21 static string format(string message, object expected, object actual) {
22 string formatted= "";
23 if (message != null && !message.equals(""))
24 formatted= message + " ";
25 string expectedstring= string.valueof(expected);
26 string actualstring= string.valueof(actual);
27 if (expectedstring.equals(actualstring))
28 return formatted + "expected: "
29 + formatclassandvalue(expected, expectedstring)
30 + " but was: " + formatclassandvalue(actual, actualstring);
31 else
32 return formatted + "expected:<" + expectedstring + "> but was:<"
33 + actualstring + ">";
34 }
35 private static string formatclassandvalue(object value, string valuestring) {
36 string classname= value == null ? "null" : value.getclass().getname();
37 return classname + "<" + valuestring + ">";
38 }
39 //對于其他方法,隻是對上面方法的包裝
40 static public void assertequals(object expected, object actual) {
41 assertequals(null, expected, actual);
42 }
43 static public void assertequals(long expected, long actual) {
44 assertequals(null, expected, actual);
45 }
46 static public void assertequals(string message, long expected, long actual) {
47 assertequals(message, (long) expected, (long) actual);
48 }
49 //對于double、float類型有點特殊,因為由于浮點的精度問題,有些時候我們允許測試邏輯傳回的結果和預計有差别,這種情況有delta表達,而且double和float都有一些特殊值,因而不能直接用“==”來比較,而需要用double.compare()方法比較,這個方法對都是double.nan放回相等。
50 static public void assertequals(string message, double expected,
51 double actual, double delta) {
52 if (double.compare(expected, actual) == 0)
53 return;
54 if (!(math.abs(expected - actual) <= delta))
55 failnotequals(message, new double(expected), new double(actual));
56 }
57 static public void assertequals(double expected, double actual, double delta) {
58 assertequals(null, expected, actual, delta);
59 }
2. 判斷數組是否相等(支援多元數組)。如果因為expecteds或actuals有一個為null而不等,抛出的assertionerror消息為:${message}: expected(actual) array was null。如果因為expecteds和actuals的長度不等,跑粗的assertionerror消息為:${message}: array lengths differed, expected.length=${expectedlength} actual.length=${actuallength}。如果因為内部元素不相等,抛出的assertionerror消息為:${message}: arrays first differed at element [i][j]….; ${notequalmessage}。關于這段邏輯的詳細實作參考exactcomparisoncriteria類。
1 public static void assertarrayequals(string message, object[] expecteds,
2 object[] actuals) throws arraycomparisonfailure {
3 internalarrayequals(message, expecteds, actuals);
4 }
5 //這個方法可以為以後自己的設計做參考,它之是以采用object表達一個數組是為了提高重用性,即這個方法可以用在double數組、int數組以及object數組上,而方法内部可以使用array提供的一些靜态方法對内部資料做一些操作。
6 private static void internalarrayequals(string message, object expecteds,
7 object actuals) throws arraycomparisonfailure {
8 new exactcomparisoncriteria().arrayequals(message, expecteds, actuals);
9 }
10 //對于其他方法,隻是對上面方法的包裝
11 public static void assertarrayequals(object[] expecteds, object[] actuals) {
12 assertarrayequals(null, expecteds, actuals);
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);
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);
35 public static void assertarrayequals(string message, int[] expecteds,
36 int[] actuals) throws arraycomparisonfailure {
37 internalarrayequals(message, expecteds, actuals);
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);
46 public static void assertarrayequals(long[] expecteds, long[] actuals) {
47 assertarrayequals(null, expecteds, actuals);
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);
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 + ">");
14 static public void assertsame(object expected, object actual) {
15 assertsame(null, expected, actual);
17 static public void assertnotsame(string message, object unexpected,
19 if (unexpected == actual)
20 failsame(message);
21 }
22 static public void assertnotsame(object unexpected, object actual) {
23 assertnotsame(null, unexpected, actual);
25 static private void failsame(string message) {
26 string formatted= "";
27 if (message != null)
28 formatted= message + " ";
29 fail(formatted + "expected not same");
30 }
4. 判斷值是否為null。null值的判斷需要自己提供消息,不然不會列印具體消息。
1 static public void assertnotnull(string message, object object) {
2 asserttrue(message, object != null);
3 }
4 static public void assertnotnull(object object) {
5 assertnotnull(null, object);
6 }
7 static public void assertnull(string message, object object) {
8 asserttrue(message, object == null);
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);
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);
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);
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);
4 public static <t> void assertthat(string reason, t actual,
5 matcher<t> matcher) {
6 if (!matcher.matches(actual)) {
7 description description= new stringdescription();
8 description.appendtext(reason);
9 description.appendtext("\nexpected: ");
10 description.appenddescriptionof(matcher);
11 description.appendtext("\n got: ");
12 description.appendvalue(actual);
13 description.appendtext("\n");
14 throw new java.lang.assertionerror(description.tostring());
15 }
說實話,我感覺到現在我可能還沒有真正了解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是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_();
5 public interface selfdescribing {
6 void describeto(description description);
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);
由于matcher的實作需要用到description,因而這裡首先對description做一些介紹。在junit中,description是對其中某個元件的描述,它可以是runner、測試方法等。但是在hamcrest中,它是一個collector,即它會周遊整個matcher鍊,以收集能比對這個matcher鍊邏輯的描述,進而可以以某種方法将這種比對邏輯描述出來,在預設實作中是通過文本的形式表達出來的,因而如上圖類圖結構所示,description的繼承結構是basedescription實作了description接口,而stringdescription則是繼承自basedescription。
description接口定義了如下方法,進而可以在matcher鍊中收集matcher的比對邏輯;貌似其實作沒有什麼多的可以講的,唯一可以講的就是在appendvalue()方法中,對不同的值類型會做不同處理,比如對null值,用”null”字元串描述;對string類型,需要對特殊字元做轉義;對字元類型,前後加引号;對short類型,用s表示,并包裹在<>中;對long類型,用l表示,也包裹在<>中;對float類型,用f表示,包裹在<>中;對數組類型,周遊數組中的所有内容;對其他類型,将其tostring值包裹在<>中;另外,對為什麼引入selfdescribingvalueiterator和selfdescribingvalue這兩個類是為了重用,這裡的重用是通過建立相同的iterator來實作的,而我自己經常的思維是定義一個接口,以封裝不同的點,比如這裡是append值的時候,對object值類型和selfdescribing類型的處理方式不同,因而可以把這部分邏輯抽取出來;感覺這裡的實作為我提供了另外一種思路的參考。對其他的感覺還是直接看代碼比較實在一些,繼續我的貼代碼風格l:
1 public interface description {
2 description appendtext(string text);
3 description appenddescriptionof(selfdescribing value);
4 description appendvalue(object value);
5 <t> description appendvaluelist(string start, string separator, string end,
6 t
values);
7 <t> description appendvaluelist(string start, string separator, string end,
8 iterable<t> values);
9 description appendlist(string start, string separator, string end,
10 iterable<? extends selfdescribing> values);
11 }
12 public abstract class basedescription implements description {
13 public description appendtext(string text) {
14 append(text);
15 return this;
16 }
17 public description appenddescriptionof(selfdescribing value) {
18 value.describeto(this);
19 return this;
20 }
21 public description appendvalue(object value) {
22 if (value == null) {
23 append("null");
24 } else if (value instanceof string) {
25 tojavasyntax((string) value);
26 } else if (value instanceof character) {
27 append('"');
28 tojavasyntax((character) value);
29 append('"');
30 } else if (value instanceof short) {
31 append('<');
32 append(valueof(value));
33 append("s>");
34 } else if (value instanceof long) {
35 append('<');
36 append(valueof(value));
37 append("l>");
38 } else if (value instanceof float) {
39 append('<');
40 append(valueof(value));
41 append("f>");
42 } else if (value.getclass().isarray()) {
43 appendvaluelist("[",", ","]", new arrayiterator(value));
44 } else {
45 append('<');
46 append(valueof(value));
47 append('>');
48 }
49 return this;
50 }
51 public <t> description appendvaluelist(string start, string separator, string end, t
values) {
52 return appendvaluelist(start, separator, end, arrays.aslist(values));
53 }
54 public <t> description appendvaluelist(string start, string separator, string end, iterable<t> values) {
55 return appendvaluelist(start, separator, end, values.iterator());
56 }
57 private <t> description appendvaluelist(string start, string separator, string end, iterator<t> values) {
58 return appendlist(start, separator, end, new selfdescribingvalueiterator<t>(values));
59 }
60 public description appendlist(string start, string separator, string end, iterable<? extends selfdescribing> values) {
61 return appendlist(start, separator, end, values.iterator());
62 }
63 private description appendlist(string start, string separator, string end, iterator<? extends selfdescribing> i) {
64 boolean separate = false;
65 append(start);
66 while (i.hasnext()) {
67 if (separate) append(separator);
68 appenddescriptionof(i.next());
69 separate = true;
70 }
71 append(end);
72 return this;
73 }
74 protected void append(string str) {
75 for (int i = 0; i < str.length(); i++) {
76 append(str.charat(i));
77 }
78 }
79 protected abstract void append(char c);
80 private void tojavasyntax(string unformatted) {
81 append('"');
82 for (int i = 0; i < unformatted.length(); i++) {
83 tojavasyntax(unformatted.charat(i));
84 }
85 append('"');
86 }
87 private void tojavasyntax(char ch) {
88 switch (ch) {
89 case '"':
90 append("\\\"");
91 break;
92 case '\n':
93 append("\\n");
94 break;
95 case '\r':
96 append("\\r");
97 break;
98 case '\t':
99 append("\\t");
100 break;
101 default:
102 append(ch);
103 }
104 }
105 }
106 public class stringdescription extends basedescription {
107 private final appendable out;
108 public stringdescription() {
109 this(new stringbuilder());
110 }
111 public stringdescription(appendable out) {
112 this.out = out;
113 }
114 public static string tostring(selfdescribing value) {
115 return new stringdescription().appenddescriptionof(value).tostring();
116 }
117 public static string asstring(selfdescribing selfdescribing) {
118 return tostring(selfdescribing);
119 }
120 protected void append(string str) {
121 try {
122 out.append(str);
123 } catch (ioexception e) {
124 throw new runtimeexception("could not write description", e);
125 }
126 }
127 protected void append(char c) {
128 try {
129 out.append(c);
130 } catch (ioexception e) {
131 throw new runtimeexception("could not write description", e);
132 }
133 }
134 public string tostring() {
135 return out.tostring();
136 }
137 }
在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);
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;
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;
114 public boolean matches(object item) {
115 return theclass.isinstance(item);
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名稱源于ui中的設計,比如在一個視窗上加入邊框、scrollbar等原件,因而成為裝飾。而在現實中,decorator引申為可以在concretcomponent的之上加入一些更多功能的類。對matcher中的decorator matcher主要氛圍兩類:1,加入更多的描述資訊,如is、describedas;2,加入一些邏輯組合,如剩下的其他matcher,isnot、anyof、allof、combinablematcher等。
is這個matcher沒有提供更多的行為,它隻是在描述前加入“is ”字元串,進而是錯誤資訊的描述資訊更加符合閱讀習慣。
1 public class is<t> extends basematcher<t> {
2 private final matcher<t> matcher;
3 public is(matcher<t> matcher) {
4 this.matcher = matcher;
5 }
6 public boolean matches(object arg) {
7 return matcher.matches(arg);
8 }
9 public void describeto(description description) {
10 description.appendtext("is ").appenddescriptionof(matcher);
12 @factory
13 public static <t> matcher<t> is(matcher<t> matcher) {
14 return new is<t>(matcher);
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 }
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
29 return new describedas<t>(description, matcher, values);
30 }
isnot這個matcher提供“非邏輯”,并在描述前加”not “字元串。
1 public class isnot<t> extends basematcher<t> {
3 public isnot(matcher<t> matcher) {
7 return !matcher.matches(arg);
10 description.appendtext("not ").appenddescriptionof(matcher);
13 public static <t> matcher<t> not(matcher<t> matcher) {
14 return new isnot<t>(matcher);
17 public static <t> matcher<t> not(t value) {
18 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;
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;
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> {
3 public allof(iterable<matcher<? extends t>> matchers) {
8 if (!matcher.matches(o)) {
9 return false;
12 return true;
15 description.appendlist("(", " and ", ")", matchers);
18 public static <t> matcher<t> allof(matcher<? extends t>
19 return allof(arrays.aslist(matchers));
22 public static <t> matcher<t> allof(iterable<matcher<? extends t>> matchers) {
23 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;
6 public boolean matches(object item) {
7 return fmatcher.matches(item);
10 description.appenddescriptionof(fmatcher);
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 }
為了是代碼更加可讀,hamcrest中的matcher都是通過一些靜态方法建構,并以靜态方式導入到一個類中,進而可以直接使用這個靜态方法。但是如果在一個源碼檔案中要用到很多matcher,就需要導入多個matcher,而且使用時還需要了解并記住多個matcher的名字,這樣顯然是不友善的,因而hamcrest架構提供了一個叫@factory的注解,并且提供一個工具,這個工具可以将所有有@factory注解的方法提取到一個單獨的類中(如corematchers),這樣,對使用者來說,我們隻需要導入這個類中的所有靜态方法就可以了,而且隻需要記住那些比較容易記住的方法即可,而不需要記住他們對應的matcher類。
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;
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;
15 public void describeto(description description) {
16 description
17 .appendtext("a collection containing ")
18 .appenddescriptionof(elementmatcher);
21 public static <t> matcher<iterable<t>> hasitem(matcher<? extends t> elementmatcher) {
22 return new iscollectioncontaining<t>(elementmatcher);
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;
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);
16 protected abstract boolean evalsubstringof(string string);
17 protected abstract string relationship();
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;
27 @override
28 protected string relationship() {
29 return "containing";
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 };
15 }
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);
12 public static void assumenoexception(throwable t) {
13 assumethat(t, nullvalue());