Testing with Hamcrest
1.概述
Hamcrest是Java生态系统中用于单元测试的著名框架。 它捆绑在JUnit中,简而言之,它使用现有谓词(称为Matcher类)进行断言。
在本教程中,我们将探索Hamcrest API,并学习如何利用它为我们的软件编写更整洁,更直观的单元测试。
2. Hamcrest设置
通过将以下依赖项添加到我们的pom.xml文件中,我们可以将Hamcrest与maven结合使用:
1 2 3 4 5 | <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-all</artifactId> <version>1.3</version> </dependency> |
始终可以在这里找到该库的最新版本。
3.示例测试
Hamcrest通常与junit和其他测试框架一起使用以进行断言。 具体而言,我们仅使用API的单个assertThat语句和适当的匹配器,而不使用junit的大量assert方法。
让我们看一个示例,该示例测试两个String是否相等。 这应该使我们对Hamcrest如何适合测试方法有一个清晰的认识:
1 2 3 4 5 6 7 8 9 |
在以下各节中,我们将介绍Hamcrest提供的其他几种常见的匹配器。
4.对象匹配器
Hamcrest提供了用于对任意Java对象进行断言的匹配器。
要断言一个对象的toString方法返回一个指定的String:
1 2 3 4 5 6 | @Test public void givenBean_whenToStringReturnsRequiredString_thenCorrect(){ Person person=new Person("Barrack","Washington"); String str=person.toString(); assertThat(person,hasToString(str)); } |
我们还可以检查一个类是否是另一个类的子类:
1 2 3 4 5 | @Test public void given2Classes_whenOneInheritsFromOther_thenCorrect(){ assertThat(Cat.class,typeCompatibleWith(Animal.class)); } } |
5.豆类比赛
我们可以使用Hamcrest的Bean匹配器来检查Java Bean的属性。
假定以下Person Bean:
1 2 3 4 5 6 7 8 9 |
我们可以检查bean是否具有属性,名称如下:
1 2 3 4 5 | @Test public void givenBean_whenHasValue_thenCorrect() { Person person = new Person("Baeldung", 25); assertThat(person, hasProperty("name")); } |
我们还可以检查Person是否具有初始化为New York的address属性:
1 2 3 4 5 | @Test public void givenBean_whenHasCorrectValue_thenCorrect() { Person person = new Person("Baeldung","New York"); assertThat(person, hasProperty("address", equalTo("New York"))); } |
我们还可以检查两个Person对象是否构造为具有相同的值:
1 2 3 4 5 6 | @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提供了用于检查集合的匹配器。
简单检查以找出Collection是否为空:
1 2 3 4 5 | @Test public void givenCollection_whenEmpty_thenCorrect() { List<String> emptyList = new ArrayList<>(); assertThat(emptyList, empty()); } |
要检查集合的大小:
1 2 3 4 5 6 | @Test public void givenAList_whenChecksSize_thenCorrect() { List<String> hamcrestMatchers = Arrays.asList( "collections","beans","text","number"); assertThat(hamcrestMatchers, hasSize(4)); } |
我们还可以使用它来断言数组具有所需的大小:
1 2 3 4 5 | @Test public void givenArray_whenChecksSize_thenCorrect() { String[] hamcrestMatchers = {"collections","beans","text","number" }; assertThat(hamcrestMatchers, arrayWithSize(4)); } |
要检查集合是否包含给定的成员,而不考虑顺序:
1 2 3 4 5 6 7 | @Test public void givenAListAndValues_whenChecksListForGivenValues_thenCorrect() { List<String> hamcrestMatchers = Arrays.asList( "collections","beans","text","number"); assertThat(hamcrestMatchers, containsInAnyOrder("beans","text","collections","number")); } |
要进一步断言Collection成员是给定顺序的,请执行以下操作:
1 2 3 4 5 6 7 | @Test public void givenAListAndValues_whenChecksListForGivenValuesWithOrder_thenCorrect() { List<String> hamcrestMatchers = Arrays.asList( "collections","beans","text","number"); assertThat(hamcrestMatchers, contains("collections","beans","text","number")); } |
要检查数组是否具有单个给定元素:
1 2 3 4 5 | @Test public void givenArrayAndValue_whenValueFoundInArray_thenCorrect() { String[] hamcrestMatchers = {"collections","beans","text","number" }; assertThat(hamcrestMatchers, hasItemInArray("text")); } |
我们还可以将替代匹配器用于同一测试:
1 2 3 4 5 | @Test public void givenValueAndArray_whenValueIsOneOfArrayElements_thenCorrect() { String[] hamcrestMatchers = {"collections","beans","text","number" }; assertThat("text", isOneOf(hamcrestMatchers)); } |
或者,我们仍然可以对其他匹配器执行相同的操作,如下所示:
1 2 3 4 5 6 |
我们还可以检查数组是否包含给定元素,而与顺序无关:
1 2 3 4 5 6 7 | @Test public void givenArrayAndValues_whenValuesFoundInArray_thenCorrect() { String[] hamcrestMatchers = {"collections","beans","text","number" }; assertThat(hamcrestMatchers, arrayContainingInAnyOrder("beans","collections","number", "text")); } |
要检查数组是否包含给定元素但按给定顺序排列:
1 2 3 4 5 6 | @Test public void givenArrayAndValues_whenValuesFoundInArrayInOrder_thenCorrect() { String[] hamcrestMatchers = {"collections","beans","text","number" }; assertThat(hamcrestMatchers, arrayContaining("collections","beans","text","number")); } |
当我们的Collection是Map时,我们可以在这些函数中使用以下匹配器:
要检查它是否包含给定密钥:
1 2 3 4 5 6 | @Test public void givenMapAndKey_whenKeyFoundInMap_thenCorrect() { Map<String, String> map = new HashMap<>(); map.put("blogname","baeldung"); assertThat(map, hasKey("blogname")); } |
和给定的值:
1 2 3 4 5 6 | @Test public void givenMapAndValue_whenValueFoundInMap_thenCorrect() { Map<String, String> map = new HashMap<>(); map.put("blogname","baeldung"); assertThat(map, hasValue("baeldung")); } |
最后是给定的条目(键,值):
1 2 3 4 5 6 | @Test public void givenMapAndEntry_whenEntryFoundInMap_thenCorrect() { Map<String, String> map = new HashMap<>(); map.put("blogname","baeldung"); assertThat(map, hasEntry("blogname","baeldung")); } |
7.号码匹配器
Number匹配器用于对Number类的变量执行断言。
要检查大于条件:
1 2 3 4 | @Test public void givenAnInteger_whenGreaterThan0_thenCorrect() { assertThat(1, greaterThan(0)); } |
检查大于或等于条件:
1 2 3 4 | @Test public void givenAnInteger_whenGreaterThanOrEqTo5_thenCorrect() { assertThat(5, greaterThanOrEqualTo(5)); } |
要检查少于状况:
1 2 3 4 | @Test public void givenAnInteger_whenLessThan0_thenCorrect() { assertThat(-1, lessThan(0)); } |
检查小于或等于条件:
1 2 3 4 | @Test public void givenAnInteger_whenLessThanOrEqTo5_thenCorrect() { assertThat(-1, lessThanOrEqualTo(5)); } |
要检查closeTo条件:
1 2 3 4 | @Test public void givenADouble_whenCloseTo_thenCorrect() { assertThat(1.2, closeTo(1, 0.5)); } |
让我们密切注意最后一个匹配器closeTo。 第一个参数是操作数,是与目标进行比较的参数,第二个参数是与操作数的允许偏差。 这意味着如果目标是操作数+偏差或操作数偏差,则测试将通过。
8.文字匹配器
Hamcrest的文本匹配器使对字符串的断言更加轻松,整洁和直观。 我们将在本节中对其进行研究。
要检查字符串是否为空:
1 2 3 4 5 | @Test public void givenString_whenEmpty_thenCorrect() { String str =""; assertThat(str, isEmptyString()); } |
要检查字符串是否为空或空:
1 2 3 4 5 | @Test public void givenString_whenEmptyOrNull_thenCorrect() { String str = null; assertThat(str, isEmptyOrNullString()); } |
要在忽略空格的情况下检查两个字符串是否相等:
1 2 3 4 5 6 |
我们还可以按照给定的顺序检查给定String中是否存在一个或多个子字符串:
1 2 3 4 5 |
最后,无论大小写如何,我们都可以检查两个字符串是否相等:
1 2 3 4 5 6 |
9.核心API
Hamcrest核心API将由第三方框架提供程序使用。 但是,它为我们提供了一些很棒的结构,以使我们的单元测试更具可读性,还提供了一些可以轻松使用的核心匹配器。
匹配器上的is结构的可读性:
1 2 3 4 5 6 |
is结构基于简单的数据类型:
1 2 3 4 5 6 |
否定匹配器上的not构造:
1 2 3 4 5 6 |
在简单数据类型上的not构造:
1 2 3 4 5 6 |
检查字符串是否包含给定的子字符串:
1 2 3 4 5 6 |
检查字符串是否以给定的子字符串开头:
1 2 3 4 5 6 |
检查字符串是否以给定的子字符串结尾:
1 2 3 4 5 6 |
检查两个对象是否属于同一实例:
1 2 3 4 5 | @Test public void given2Objects_whenSameInstance_thenCorrect() { Cat cat=new Cat(); assertThat(cat, sameInstance(cat)); } |
检查对象是否是给定类的实例:
1 2 3 4 5 | @Test public void givenAnObject_whenInstanceOfGivenClass_thenCorrect() { Cat cat=new Cat(); assertThat(cat, instanceOf(Cat.class)); } |
检查集合的所有成员是否满足条件:
1 2 3 4 5 6 | @Test public void givenList_whenEachElementGreaterThan0_thenCorrect() { List<Integer> list = Arrays.asList(1, 2, 3); int baseCase = 0; assertThat(list, everyItem(greaterThan(baseCase))); } |
检查字符串是否不为空:
1 2 3 4 5 | @Test public void givenString_whenNotNull_thenCorrect() { String str ="notnull"; assertThat(str, notNullValue()); } |
将条件链接在一起,当目标满足任何条件时测试通过,类似于逻辑或:
1 2 3 4 5 6 7 |
将条件链接在一起,仅在目标满足所有条件时才通过测试,类似于逻辑与:
1 2 3 4 5 6 7 |
10.自定义匹配器
我们可以通过扩展TypeSafeMatcher来定义自己的匹配器。 在本节中,我们将创建一个自定义匹配器,该匹配器仅在目标为正整数时才允许测试通过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; } } |
我们只需要实现matchSafely方法(该方法检查目标确实是一个正整数),以及实现describeTo方法(如果测试未通过的话,该方法将生成失败消息)。
这是使用我们新的自定义匹配器的测试:
1 2 3 4 5 | @Test public void givenInteger_whenAPositiveValue_thenCorrect() { int num = 1; assertThat(num, isAPositiveInteger()); } |
由于传递了一个非正整数,这是一条失败消息:
1 | java.lang.AssertionError: Expected: a positive integer but: was <-1> |
11.结论
在本教程中,我们探索了Hamcrest API,并了解了如何使用它编写更好和更可维护的单元测试。
所有这些示例和代码段的完整实现都可以在我的Hamcrest github项目中找到。