1. 概览
Hamcrest 是Java生态中广为人知的单元测试框架。它已集成在JUnit中,简单来说,它通过使用预定义的匹配器(matcher)来执行断言。
本文将深入探索Hamcrest的API,展示如何利用它编写更整洁、更直观的单元测试。
2. Hamcrest环境搭建
通过Maven使用Hamcrest,需在pom.xml
中添加以下依赖:
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
</dependency>
最新版本可在此查找。
3. 示例测试
Hamcrest常与JUnit等测试框架配合使用。核心特点是——摒弃JUnit的多种assert方法,统一使用assertThat
配合匹配器。
来看个案例:测试两个字符串忽略大小写是否相等,直观展示Hamctest的用法:
public class StringMatcherTest {
@Test
public void given2Strings_whenEqual_thenCorrect() {
String a = "foo";
String b = "FOO";
assertThat(a, equalToIgnoringCase(b));
}
}
后续章节将介绍Hamcrest提供的其他常用匹配器。
4. 对象匹配器
Hamcrest为任意Java对象提供断言匹配器。
验证toString()
输出
@Test
public void givenBean_whenToStringReturnsRequiredString_thenCorrect(){
Person person=new Person("Barrack", "Washington");
String str=person.toString();
assertThat(person,hasToString(str));
}
验证继承关系
@Test
public void given2Classes_whenOneInheritsFromOther_thenCorrect(){
assertThat(Cat.class,typeCompatibleWith(Animal.class));
}
}
5. JavaBean匹配器
Hamcrest的Bean匹配器用于检查JavaBean属性。
假设有以下Person
类:
public class Person {
String name;
String address;
public Person(String personName, String personAddress) {
name = personName;
address = personAddress;
}
}
检查属性是否存在
@Test
public void givenBean_whenHasValue_thenCorrect() {
Person person = new Person("Baeldung", 25);
assertThat(person, hasProperty("name"));
}
检查属性值是否匹配
@Test
public void givenBean_whenHasCorrectValue_thenCorrect() {
Person person = new Person("Baeldung", "New York");
assertThat(person, hasProperty("address", equalTo("New York")));
}
检查两个Bean属性值相等
@Test
public void given2Beans_whenHavingSameValues_thenCorrect() {
Person person1 = new Person("Baeldung", "New York");
Person person2 = new Person("Baeldung", "New York");
assertThat(person1, samePropertyValuesAs(person2));
}
6. 集合匹配器
Hamcrest提供丰富的集合操作匹配器。
检查空集合
@Test
public void givenCollection_whenEmpty_thenCorrect() {
List<String> emptyList = new ArrayList<>();
assertThat(emptyList, empty());
}
检查集合大小
@Test
public void givenAList_whenChecksSize_thenCorrect() {
List<String> hamcrestMatchers = Arrays.asList(
"collections", "beans", "text", "number");
assertThat(hamcrestMatchers, hasSize(4));
}
检查数组大小
@Test
public void givenArray_whenChecksSize_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat(hamcrestMatchers, arrayWithSize(4));
}
检查集合元素(无序)
@Test
public void givenAListAndValues_whenChecksListForGivenValues_thenCorrect() {
List<String> hamcrestMatchers = Arrays.asList(
"collections", "beans", "text", "number");
assertThat(hamcrestMatchers,
containsInAnyOrder("beans", "text", "collections", "number"));
}
检查集合元素(有序)
@Test
public void givenAListAndValues_whenChecksListForGivenValuesWithOrder_thenCorrect() {
List<String> hamcrestMatchers = Arrays.asList(
"collections", "beans", "text", "number");
assertThat(hamcrestMatchers,
contains("collections", "beans", "text", "number"));
}
检查数组包含特定元素
@Test
public void givenArrayAndValue_whenValueFoundInArray_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat(hamcrestMatchers, hasItemInArray("text"));
}
替代写法:元素在数组中
@Test
public void givenValueAndArray_whenValueIsOneOfArrayElements_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat("text", isOneOf(hamcrestMatchers));
}
替代写法:元素存在于数组
@Test
public void givenValueAndArray_whenValueFoundInArray_thenCorrect() {
String[] array = new String[] { "collections", "beans", "text",
"number" };
assertThat("beans", isIn(array));
}
检查数组包含所有元素(无序)
@Test
public void givenArrayAndValues_whenValuesFoundInArray_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat(hamcrestMatchers,
arrayContainingInAnyOrder("beans", "collections", "number",
"text"));
}
检查数组包含所有元素(有序)
@Test
public void givenArrayAndValues_whenValuesFoundInArrayInOrder_thenCorrect() {
String[] hamcrestMatchers = { "collections", "beans", "text", "number" };
assertThat(hamcrestMatchers,
arrayContaining("collections", "beans", "text", "number"));
}
Map操作:检查Key存在
@Test
public void givenMapAndKey_whenKeyFoundInMap_thenCorrect() {
Map<String, String> map = new HashMap<>();
map.put("blogname", "baeldung");
assertThat(map, hasKey("blogname"));
}
Map操作:检查Value存在
@Test
public void givenMapAndValue_whenValueFoundInMap_thenCorrect() {
Map<String, String> map = new HashMap<>();
map.put("blogname", "baeldung");
assertThat(map, hasValue("baeldung"));
}
Map操作:检查Entry存在
@Test
public void givenMapAndEntry_whenEntryFoundInMap_thenCorrect() {
Map<String, String> map = new HashMap<>();
map.put("blogname", "baeldung");
assertThat(map, hasEntry("blogname", "baeldung"));
}
7. 数字匹配器
用于数字类型断言,操作简单粗暴。
大于判断
@Test
public void givenAnInteger_whenGreaterThan0_thenCorrect() {
assertThat(1, greaterThan(0));
}
大于等于判断
@Test
public void givenAnInteger_whenGreaterThanOrEqTo5_thenCorrect() {
assertThat(5, greaterThanOrEqualTo(5));
}
小于判断
@Test
public void givenAnInteger_whenLessThan0_thenCorrect() {
assertThat(-1, lessThan(0));
}
小于等于判断
@Test
public void givenAnInteger_whenLessThanOrEqTo5_thenCorrect() {
assertThat(-1, lessThanOrEqualTo(5));
}
浮点数近似判断
@Test
public void givenADouble_whenCloseTo_thenCorrect() {
assertThat(1.2, closeTo(1, 0.5));
}
注意:
closeTo
的第二个参数是允许偏差。测试通过条件:目标值在[1-0.5, 1+0.5]区间内。
8. 文本匹配器
让字符串断言更直观,避免手动处理各种边界情况。
检查空字符串
@Test
public void givenString_whenEmpty_thenCorrect() {
String str = "";
assertThat(str, isEmptyString());
}
检查空或null字符串
@Test
public void givenString_whenEmptyOrNull_thenCorrect() {
String str = null;
assertThat(str, isEmptyOrNullString());
}
忽略空白字符比较
@Test
public void given2Strings_whenEqualRegardlessWhiteSpace_thenCorrect() {
String str1 = "text";
String str2 = " text ";
assertThat(str1, equalToIgnoringWhiteSpace(str2));
}
检查子串顺序存在
@Test
public void givenString_whenContainsGivenSubstring_thenCorrect() {
String str = "calligraphy";
assertThat(str, stringContainsInOrder(Arrays.asList("call", "graph")));
}
忽略大小写比较
@Test
public void given2Strings_whenEqual_thenCorrect() {
String a = "foo";
String b = "FOO";
assertThat(a, equalToIgnoringCase(b));
}
9. 核心API
Hamcrest核心API虽面向框架开发者,但提供了增强可读性的语法和基础匹配器。
使用is
增强可读性
@Test
public void given2Strings_whenIsEqualRegardlessWhiteSpace_thenCorrect() {
String str1 = "text";
String str2 = " text ";
assertThat(str1, is(equalToIgnoringWhiteSpace(str2)));
}
is
用于基本类型
@Test
public void given2Strings_whenIsEqual_thenCorrect() {
String str1 = "text";
String str2 = "text";
assertThat(str1, is(str2));
}
使用not
取反
@Test
public void given2Strings_whenIsNotEqualRegardlessWhiteSpace_thenCorrect() {
String str1 = "text";
String str2 = " texts ";
assertThat(str1, not(equalToIgnoringWhiteSpace(str2)));
}
not
用于基本类型
@Test
public void given2Strings_whenNotEqual_thenCorrect() {
String str1 = "text";
String str2 = "texts";
assertThat(str1, not(str2));
}
检查包含子串
@Test
public void givenAStrings_whenContainsAnotherGivenString_thenCorrect() {
String str1 = "calligraphy";
String str2 = "call";
assertThat(str1, containsString(str2));
}
检查前缀
@Test
public void givenAString_whenStartsWithAnotherGivenString_thenCorrect() {
String str1 = "calligraphy";
String str2 = "call";
assertThat(str1, startsWith(str2));
}
检查后缀
@Test
public void givenAString_whenEndsWithAnotherGivenString_thenCorrect() {
String str1 = "calligraphy";
String str2 = "phy";
assertThat(str1, endsWith(str2));
}
检查同一实例
@Test
public void given2Objects_whenSameInstance_thenCorrect() {
Cat cat=new Cat();
assertThat(cat, sameInstance(cat));
}
检查实例类型
@Test
public void givenAnObject_whenInstanceOfGivenClass_thenCorrect() {
Cat cat=new Cat();
assertThat(cat, instanceOf(Cat.class));
}
检查集合所有元素满足条件
@Test
public void givenList_whenEachElementGreaterThan0_thenCorrect() {
List<Integer> list = Arrays.asList(1, 2, 3);
int baseCase = 0;
assertThat(list, everyItem(greaterThan(baseCase)));
}
检查非null
@Test
public void givenString_whenNotNull_thenCorrect() {
String str = "notnull";
assertThat(str, notNullValue());
}
逻辑OR条件(满足任一)
@Test
public void givenString_whenMeetsAnyOfGivenConditions_thenCorrect() {
String str = "calligraphy";
String start = "call";
String end = "foo";
assertThat(str, anyOf(startsWith(start), containsString(end)));
}
逻辑AND条件(全部满足)
@Test
public void givenString_whenMeetsAllOfGivenConditions_thenCorrect() {
String str = "calligraphy";
String start = "call";
String end = "phy";
assertThat(str, allOf(startsWith(start), endsWith(end)));
}
10. 自定义匹配器
通过继承TypeSafeMatcher
创建自定义匹配器。以下示例实现正整数校验:
public class IsPositiveInteger extends TypeSafeMatcher<Integer> {
public void describeTo(Description description) {
description.appendText("a positive integer");
}
@Factory
public static Matcher<Integer> isAPositiveInteger() {
return new IsPositiveInteger();
}
@Override
protected boolean matchesSafely(Integer integer) {
return integer > 0;
}
}
关键点:
matchesSafely
:实现核心校验逻辑describeTo
:提供失败时的描述信息
使用自定义匹配器
@Test
public void givenInteger_whenAPositiveValue_thenCorrect() {
int num = 1;
assertThat(num, isAPositiveInteger());
}
失败案例输出
java.lang.AssertionError: Expected: a positive integer but: was <-1>
11. 总结
本文深入探索了Hamcrest API,展示了如何利用它编写更优雅、更易维护的单元测试。所有示例代码可在我的Hamcrest GitHub项目中获取完整实现。