天天看點

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

初次用文字的方式記錄讀源碼的過程,不知道怎麼寫,感覺有點貼代碼的嫌疑。不過中間還是加入了一些自己的了解和心得,希望以後能夠慢慢的改進,感興趣的童鞋湊合着看吧,感覺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)。

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

線條太多了,圖畫的有點亂,之是以把那些所有對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

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

 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

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

 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

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

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>

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

 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>

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

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>

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

 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

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

 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

深入JUnit源碼之Assert與Hamcrest 深入JUnit源碼之Assert與Hamcrest

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

繼續閱讀