使用Hamcrest写Unit Test

By | 2016年10月23日

Hamcrest简介

Hamcrest首先出现于Java语言,目前支持以下编程语言:Java, Python, Ruby, Objective-C, PHP, Erlang, Swift。

Hamcrest不是测试框架,只是一个比较器(matcher),判断某个结果是否和预期的一样。目前比较器进化有三个阶段:

  1. assert(logical statement),判断一个布尔表达式是否为true,例如Java本身的,assert a == b : “a is different with b“;
  2. assertEquals(expected, actual),判断实际数据和预期的是否一致,因为数据类型的不同,产生了很多重载方法;
  3. assertThat(actual, matcher expression),这是Hamcrest的格式,第一个参数是实际数据,第二个参数是比较表达式。

Hamcrest的初衷是提高unit test的可读性,进而提高unit test的可维护性,并且Hamcrest比较失败时的message可读性更好。

最新版本是1.4-atlassian-1,但用的最多的是1.3,下面是Maven中的下载坐标:

<dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-library</artifactId>
        <version>1.3</version>
</dependency>

P.S.

Junit从4.4版本开始,就引入了Hamcrest的核心比较器(hamcrest-core-{version}.jar),可以不用单独引入Hamcrest,但是核心比较器包含的matcher不是很多,最好还是单独引入一个Hamrcrest。

Hamcrest使用总结

hamcrest的比较格式:assertThat(actual, {expected matcher expression});

1. 字符串比较

1.1 equalTo

判断字符串是否相等。

String name = "Hello";
assertThat(name, equalTo("Hello"));

1.2 equalToIgnoringCase

比较时,忽略字符串大小写。

String name = "HELLO";
assertThat(name, equalToIgnoringCase("hello"));

1.3 equalToIgnoringWhiteSpace

去掉actual string和expected string的前后whitspace,字符串中间多个whitspace变成一个whitespace,然后比较是否相等。

String name = "  he         llo      ";
assertThat(name, equalToIgnoringWhiteSpace("     he llo "));

1.4 containsString

判断是否包含某个字符串。

String name = "Hello the world";
assertThat(name, containsString("the"));

1.5 startsWith, endsWith

判断是否以某个字符串开头或结尾。

String name = "Hello the world";
assertThat(name, startsWith("Hello"));
assertThat(name, endsWith("world"));

1.6 isEmptyString, isEmptyOrNullString

判断字符串是否为空或null。

String name = "Hello the world";
assertThat(name, isEmptyString());
assertThat(name, isEmptyOrNullString());

2. 数值比较(byte, int, long, float, double)

2.1 比较数值相等

hamcrest将原子值封装成对象(int –> Integer),然后调用对象的euqals方法。浮点数不能控制比较精度,需要控制精度,就用assertEquals(d1, d2, delta)。

double price = 11.23;
assertThat(price, equalTo(11.23));

P.S.

下面关于数值的比较,hamcrest将其封装成对象(int –> Integer),然后调用对象的compareTo方法,所以就算不是数值类型,只要其实现了Comparable接口,也可以进行比较。

2.2 greaterThan, lessThan

判断是否大于或小于某个数。

double price = 11.23;
assertThat(price, greaterThan(10.01));
assertThat(price, lessThan(13.22));

2.3 greaterThanOrEqualTo, lessThanOrEqualTo

判断是否大于等于或者小于等于某个数。

double price = 11.23;
assertThat(price, greaterThanOrEqualTo(10.01));
assertThat(price, lessThanOrEqualTo(13.22));

3. 布尔值比较

使用equalTo,个人感觉这个不如Junit的assertTrue和assertFalse好。

boolean isLocked = true;
assertThat(isLocked, equalTo(true));

// Junit way of checking boolean
assertTrue(isLocked);
assertFalse(isLocked);

4. 对象比较

4.1 euqalTo

调用对象的equals方法比较,actual object 和 expceted object可以是null,仅当它们都是null时,才会比较成功。两者如果只有一个是null,会抛出null pointer exception。

Person p1 = new Person("Monday");
Person p2 = new Person("Monday");
assertThat(p1, equalTo(p2));     // pass
assertThat(null, equalTo(null)); // pass

// Below two case throw null pointer exception.
assertThat(p1, equalTo(null));
assertThat(null, equalTo(p1));

4.2 sameInstance

比较是否是同一个对象。

assertThat(person1, sameInstance(person2));

4.3 instanceOf

判断是否是某个类的实例。

String str = "hello";
assertThat(str, instanceOf(String.class));

4.4 nullValue, notNullValue

判断一个对象是否为null。

assertThat(obj, nullValue());
assertThat(obj, notNullValue());

5. 数组比较

5.1 equalTo,arrayContaining

它俩效果一样,当两个数组长度相等,且按顺序每个元素相同时,他们相等。

Integer[] nums = {1, 2, 3};
assertThat(nums, equalTo(new int[] {1, 2, 3}));
assertThat(nums, arrayContaining(1, 2, 3));

5.2 arrayWithSize

判断数组大小。

assertThat(nums, arrayWithSize(3));

5.3 arrayContainingInAnyOrder

两数组,长度相同,不考虑顺序,它们包含相同的元素,则断言为true。

Integer[] nums = {1, 2, 3};
assertThat(nums, arrayContainingInAnyOrder(2, 1, 3));

6. Map比较

hasEntry判断是否包含某个<key, value>,hasKey判断是否包含某个key,hasValue判断是否包含某个value。

Map<String, Integer> map = Maps.newHashMap();
assertThat(map, hasEntry("key", "value"));
assertThat(map, hasKey("key"));
assertThat(map, hasValue("value"));

7. Collection比较

常用的Collection有List和Set。下面的例子,是对List<String> list = List.newArrayList(1, 2, 3),做的操作,Set操作一样。

7.1 hasSize

判断集合的大小。

assertThat(list, hasSize(3));

7.2 hasItem, hasItems

判断是否包含指定的一个元素或多个元素。

List<Integer> list = Lists.newArrayList(1, 2, 3);
assertThat(list, hasItem(2));
assertThat(list, hasItems(3, 3, 2));

hasItem嵌套anyOf或allOf,可以对元素指定多个判断条件。

List<String> strs = Lists.newArrayList();
assertThat(strs, hasItem(anyOf(equalTo("Alex"), equalTo("alzhang"))));
assertThat(strs, hasItem(allOf(startsWith("a"), endsWith("g"))));

7.3 contains

两个List的size相同,并且按顺序,每个元素相等。

List<Integer> list = Lists.newArrayList(1, 2, 3);
assertThat(list, contains(1, 2, 3));

7.4 containsInAnyOrder

两个List的size相同,不考虑顺序,他们包含相同的元素。

List<Integer> list = Lists.newArrayList(1, 2, 3);
assertThat(list, containsInAnyOrder(2, 3, 1));

7.5 everyItem

对集合的每个元素,进行match,只有都match了,断言才为true。

List<Integer> list = Lists.newArrayList(1, 2, 3);
assertThat(list, everyItem(greaterThan(0)));

注:我本以为有anyItem方法,只要有一个match了,断言就为true,但是我没找到该方法。

8. Bean对象比较

Bean就是数据对象,包含大量的getter/setter方法。

8.1 hasProperty

判断Bean是否有某个类变量,该变量至少有一个get方法或set方法,否则Hamcrest不认为其是个property。

assertThat(person, hasProperty("name"));
assertThat(person, hasProperty("name", equalTo("abcd")));

8.2 samePropertyValuesAs

判断两个Bean是否有相同的Property,并且它们的Value也相等。

class Flight {
  String id;
  String country;
  String speed;
  // Getters, Settes.
}

assertThat(flight1, samePropertyValuesAs(flight2))

9. 与或非,逻辑组合

9.1 allOf

对一个元素的判断,需要同时成立。

int width = 13;
assertThat(width, allOf(greaterThan(10), lessThanOrEqualTo(13)));

assertThat(person, allOf(hasProperty("name"), hasProperty("gender")));

9.2 anyOf

对一个元素的判断,只要有一个成立。

assertThat(width, anyOf(equalTo(0), greaterThan(100)));
assertThat(person.getName(), anyOf(equalTo("Hillary"), equalTo("Trump")));

9.3 not

对一个判断取反。

assertThat(list, not(hasItem(2)));
assertThat(str, not(startsWith("http")));

10. 自定义Matcher

针对某些特殊的比较,可以写自己的matcher。下面的例子,写了一个matcher,用于判断一个Session是否Done(运行结束)。

class SessionMatcher extends TypeSafeMatcher<Session> {
  @Override
  public void describeTo(Description description) {
    // Describe what this matcher expects.
    description.appendText("Session status shall be done "
        + "and session result shall be generated.");
  }

  @Override
  protected boolean matchesSafely(Session session) {
    return "done".equals(session.getStatus()) && session.hasResult();
  }
}

Session session = new Session();
assertThat(session, new SessionMatcher());

11. 其它

11.1 is

可以用来包裹任何matcher,提高可读性,可有可无,看个人喜好。

String str = "hello";
assertThat(str, is(equalTo("Hello")));
assertThat(str, is(startsWith("h")));
assertThat(str, is(instanceOf(String.class)));