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的斷言。
-
@Test
-
public void test_with_junit_assert() {
-
int expected =
51;
-
int actual =
51;
-
-
assertEquals(
"failure - They are not same!", expected, actual);
-
}
-
-
@Test
-
public void test_with_hamcrest_assertThat() {
-
int expected =
51;
-
int actual =
51;
-
-
assertThat(
"failure - They are not same!", actual, equalTo(expected));
-
}
個人感覺有兩個明顯的區別:
1. 參數順序。兩者的expected 和 actual 前后順序是相反的。
2. Hamcrest 幾乎總是直接使用對象。它的語法更符合函數式編程的風格。
這點很好理解了,Junit 總是獲取值后再比較,因為比較的是簡單的值,因此被比較的放在前面更符合習慣。
而Hamcrest 是直接對測試結果的對象進行一些更復雜的匹配。
支持語言
Hamcrest 支持以下幾種語言,詳情見http://hamcrest.org/
Java
Python
Ruby
Objective-C
PHP
Erlang
Swift
Hamcrest匹配器
Hamcrest 提供了很強大的一些api 供我們進行測試斷言。
-
核心:
-
anything - 總是匹配,如果你不關心測試下的對象是什么是有用的
-
describedAs - 添加一個定制的失敗表述裝飾器
-
is - 改進可讀性裝飾器 - 見下 “Sugar”
-
邏輯:
-
allOf - 如果所有匹配器都匹配才匹配,像Java里的&&
-
anyOf - 如果任何匹配器匹配就匹配,像Java里的||
-
not - 如果包裝的匹配器不匹配器時匹配,反之亦然
-
對象:
-
equalTo - 測試對象相等使用Object.equals方法
-
hasToString - 測試Object.toString方法
-
instanceOf, isCompatibleType - 測試類型
-
notNullValue, nullValue - 測試
null
-
sameInstance - 測試對象實例
-
Beans:
-
hasProperty - 測試JavaBeans屬性
-
集合:
-
array - 測試一個數組元素test an array’s elements against an array of matchers
-
hasEntry, hasKey, hasValue - 測試一個Map包含一個實體,鍵或者值
-
hasItem, hasItems - 測試一個集合包含一個元素
-
hasItemInArray - 測試一個數組包含一個元素
-
數字:
-
closeTo - 測試浮點值接近給定的值
-
greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - 測試次序
-
文本:
-
equalToIgnoringCase - 測試字符串相等忽略大小寫
-
equalToIgnoringWhiteSpace - 測試字符串忽略空白
-
containsString, endsWith, startsWith - 測試字符串匹配
這些API 幾乎覆蓋了我們測試斷言的所有情況。再提供良好閱讀性的情況下,減少了一些取值、循環、類型判斷等代碼的編寫。
不足
這里說的不足,只是本人使用過程中遇到的問題。如果大牛發現Hamcrest 現有的api 支持,歡迎指正。
hasProperty
先看一個復雜的層級結構。
-
public
class MyPerson {
-
private Address address;
-
private String name;
-
private String fullName;
-
-
// ...
-
}
其中 Address 類如下:
-
public
class Address {
-
-
private String companyAddress;
-
private String personalAddress;
-
-
// ...
-
}
這里我們測試一個person對象,有 companyAddress 屬性。
-
@Test
-
public void test_with_complex_property() {
-
MyPerson p =
new MyPerson();
-
Address address =
new Address();
-
address.setCompanyAddress(
"");
-
p.setAddress(address);
-
assertThat(
"failure has no address property !", p, hasProperty(
"address"));
-
-
// would failed
-
// assertThat("failure has no address.companyAddress !", p, hasProperty("address.companyAddress"));
-
}
這個單元測試的第二個斷言出錯。也就是說斷言 hasProperty 時,不能跨層。這點就沒有 rest-assured 的API 用着那么方便了。
這里我想了個辦法如下:
assertThat("failure has no address property !", p, hasProperty("address", hasProperty("companyAddress")));
進行了一次嵌套后,就可以滿足我們的需要了。
集合的泛型
這里我們要處理集合,斷言每個元素都有屬性:
-
@Test
-
public void test_with_list_generics() {
-
List persons =
new ArrayList<MyPerson>();
-
MyPerson p =
new MyPerson();
-
p.setName(
"KaKa");
-
persons.add(p);
-
-
MyPerson p2 =
new MyPerson();
-
p2.setName(
"Hust");
-
persons.add(p2);
-
-
// compile error
-
// assertThat("failure has no address property !", persons, everyItem(hasProperty("address")));
-
assertThat(
"failure has no address property !", (List<Object>)persons, everyItem(hasProperty(
"address")));
-
}
這我就沒法理解了。顯然,被注釋的那句斷言直接編譯失敗!然后向上轉型后,反而成功。
對象自身屬性比較
還用上面的例子,每個 person對象 都有 name 和 fullName 兩個屬性,斷言 fullName 是startwith name 屬性。
-
@Test
-
public void test_with_list_compare_with_self() {
-
// 加上 類型
-
List persons =
new ArrayList<MyPerson>();
-
MyPerson p =
new MyPerson();
-
p.setName(
"KaKa");
-
p.setFullName(
"KaKa Zhang");
-
persons.add(p);
-
-
MyPerson p2 =
new MyPerson();
-
p2.setName(
"Hust");
-
p.setFullName(
"Hust Zhang");
-
persons.add(p2);
-
-
// 這里沒法寫, person對象 屬性 startsWith 另一個屬性
-
assertThat((List<Object>)persons, everyItem(hasProperty(
"name", startsWith(
""))));
-
}
這時候只能循環來斷言,某個屬性startwWith 另一個屬性了。
-
@Test
-
public void test_with_list_compare_with_self() {
-
// 加上 類型
-
List<MyPerson> persons =
new ArrayList<MyPerson>();
-
MyPerson p =
new MyPerson();
-
p.setName(
"KaKa");
-
p.setFullName(
"KaKa Zhang");
-
persons.add(p);
-
-
MyPerson p2 =
new MyPerson();
-
p2.setName(
"Hust");
-
p2.setFullName(
"Hust Zhang");
-
persons.add(p2);
-
-
// 還是用循環來做
-
for (MyPerson person : persons) {
-
assertThat(person.getFullName().startsWith(
""), is(
true));
-
}
-
}
上面的這幾個時我遇到幾個情況。如果大牛有更好的做法,歡迎指點。
</div>