Hamcrest 總結


Junit

JUnit框架用一組assert方法封裝了一些常用的斷言。這些assert方法可以幫我們簡化單元測試的編寫。這樣的話,Junit就可以根據這些斷言是否拋出 AssertionFailedError 錯誤來判斷測試用例的執行結果。

 

Hamcrest

使用過Junit 的應該有過體驗:在實際開發中,一些基本的斷言,如eqaul,null,true它們的可讀性並不是很好。而且很多時候我們要比較對象、集合、Map等數據結構。這樣我們要么進行大段的字段獲取再斷言。或者干脆自己編寫表達式並斷言其結果。
JUnit4.4引入了Hamcrest框架,Hamcest提供了一套匹配符Matcher,這些匹配符更接近自然語言,可讀性高,更加靈活。

Hamcrest 提供了大量被稱為“匹配器”的方法。其中每個匹配器都設計用於執行特定的比較操作。Hamcrest的可擴展性很好,讓你能夠創建自定義的匹配器。最重要的是,JUnit也包含了Hamcrest的核心,提供了對Hamcrest的原生支持,可以直接使用Hamcrest。當然要使用功能齊備的Hamcrest,還是要引入對它的依賴。
 

看個對比例子,前者使用Junit的 斷言,后者使用 Hamcrest的斷言。

 


   
   
   
           
  1. @Test
  2. public void test_with_junit_assert() {
  3. int expected = 51;
  4. int actual = 51;
  5. assertEquals( "failure - They are not same!", expected, actual);
  6. }
  7. @Test
  8. public void test_with_hamcrest_assertThat() {
  9. int expected = 51;
  10. int actual = 51;
  11. assertThat( "failure - They are not same!", actual, equalTo(expected));
  12. }

個人感覺有兩個明顯的區別:
1. 參數順序。兩者的expected 和 actual 前后順序是相反的。
2. Hamcrest 幾乎總是直接使用對象。它的語法更符合函數式編程的風格。
這點很好理解了,Junit 總是獲取值后再比較,因為比較的是簡單的值,因此被比較的放在前面更符合習慣。
而Hamcrest 是直接對測試結果的對象進行一些更復雜的匹配。

 

 

支持語言

Hamcrest 支持以下幾種語言,詳情見http://hamcrest.org/
Java
Python
Ruby
Objective-C
PHP
Erlang
Swift

 

 

Hamcrest匹配器

Hamcrest 提供了很強大的一些api 供我們進行測試斷言。

 

 


   
   
   
           
  1. 核心:
  2. anything - 總是匹配,如果你不關心測試下的對象是什么是有用的
  3. describedAs - 添加一個定制的失敗表述裝飾器
  4. is - 改進可讀性裝飾器 - 見下 “Sugar”
  5. 邏輯:
  6. allOf - 如果所有匹配器都匹配才匹配,像Java里的&&
  7. anyOf - 如果任何匹配器匹配就匹配,像Java里的||
  8. not - 如果包裝的匹配器不匹配器時匹配,反之亦然
  9. 對象:
  10. equalTo - 測試對象相等使用Object.equals方法
  11. hasToString - 測試Object.toString方法
  12. instanceOf, isCompatibleType - 測試類型
  13. notNullValue, nullValue - 測試 null
  14. sameInstance - 測試對象實例
  15. Beans:
  16. hasProperty - 測試JavaBeans屬性
  17. 集合:
  18. array - 測試一個數組元素test an array’s elements against an array of matchers
  19. hasEntry, hasKey, hasValue - 測試一個Map包含一個實體,鍵或者值
  20. hasItem, hasItems - 測試一個集合包含一個元素
  21. hasItemInArray - 測試一個數組包含一個元素
  22. 數字:
  23. closeTo - 測試浮點值接近給定的值
  24. greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - 測試次序
  25. 文本:
  26. equalToIgnoringCase - 測試字符串相等忽略大小寫
  27. equalToIgnoringWhiteSpace - 測試字符串忽略空白
  28. containsString, endsWith, startsWith - 測試字符串匹配

這些API 幾乎覆蓋了我們測試斷言的所有情況。再提供良好閱讀性的情況下,減少了一些取值、循環、類型判斷等代碼的編寫。
 

 

不足

這里說的不足,只是本人使用過程中遇到的問題。如果大牛發現Hamcrest 現有的api 支持,歡迎指正。

 

hasProperty

先看一個復雜的層級結構。

 


   
   
   
           
  1. public class MyPerson {
  2. private Address address;
  3. private String name;
  4. private String fullName;
  5. // ...
  6. }

 

其中 Address 類如下:

 


   
   
   
           
  1. public class Address {
  2. private String companyAddress;
  3. private String personalAddress;
  4. // ...
  5. }

這里我們測試一個person對象,有 companyAddress 屬性。

 

 


   
   
   
           
  1. @Test
  2. public void test_with_complex_property() {
  3. MyPerson p = new MyPerson();
  4. Address address = new Address();
  5. address.setCompanyAddress( "");
  6. p.setAddress(address);
  7. assertThat( "failure has no address property !", p, hasProperty( "address"));
  8. // would failed
  9. // assertThat("failure has no address.companyAddress !", p, hasProperty("address.companyAddress"));
  10. }

這個單元測試的第二個斷言出錯。也就是說斷言 hasProperty 時,不能跨層。這點就沒有 rest-assured 的API 用着那么方便了。

 

這里我想了個辦法如下:

 

assertThat("failure has no address property !", p, hasProperty("address", hasProperty("companyAddress")));
  
  
  
          

進行了一次嵌套后,就可以滿足我們的需要了。

 

集合的泛型

 

這里我們要處理集合,斷言每個元素都有屬性:

 


   
   
   
           
  1. @Test
  2. public void test_with_list_generics() {
  3. List persons = new ArrayList<MyPerson>();
  4. MyPerson p = new MyPerson();
  5. p.setName( "KaKa");
  6. persons.add(p);
  7. MyPerson p2 = new MyPerson();
  8. p2.setName( "Hust");
  9. persons.add(p2);
  10. // compile error
  11. // assertThat("failure has no address property !", persons, everyItem(hasProperty("address")));
  12. assertThat( "failure has no address property !", (List<Object>)persons, everyItem(hasProperty( "address")));
  13. }

這我就沒法理解了。顯然,被注釋的那句斷言直接編譯失敗!然后向上轉型后,反而成功。

 

對象自身屬性比較

還用上面的例子,每個 person對象 都有 namefullName 兩個屬性,斷言 fullName 是startwith name 屬性。


   
   
   
           
  1. @Test
  2. public void test_with_list_compare_with_self() {
  3. // 加上 類型
  4. List persons = new ArrayList<MyPerson>();
  5. MyPerson p = new MyPerson();
  6. p.setName( "KaKa");
  7. p.setFullName( "KaKa Zhang");
  8. persons.add(p);
  9. MyPerson p2 = new MyPerson();
  10. p2.setName( "Hust");
  11. p.setFullName( "Hust Zhang");
  12. persons.add(p2);
  13. // 這里沒法寫, person對象 屬性 startsWith 另一個屬性
  14. assertThat((List<Object>)persons, everyItem(hasProperty( "name", startsWith( ""))));
  15. }

這時候只能循環來斷言,某個屬性startwWith 另一個屬性了。

 

 


   
   
   
           
  1. @Test
  2. public void test_with_list_compare_with_self() {
  3. // 加上 類型
  4. List<MyPerson> persons = new ArrayList<MyPerson>();
  5. MyPerson p = new MyPerson();
  6. p.setName( "KaKa");
  7. p.setFullName( "KaKa Zhang");
  8. persons.add(p);
  9. MyPerson p2 = new MyPerson();
  10. p2.setName( "Hust");
  11. p2.setFullName( "Hust Zhang");
  12. persons.add(p2);
  13. // 還是用循環來做
  14. for (MyPerson person : persons) {
  15. assertThat(person.getFullName().startsWith( ""), is( true));
  16. }
  17. }

上面的這幾個時我遇到幾個情況。如果大牛有更好的做法,歡迎指點。
 

      </div>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM