使用 Hamcrest 编写富有表现力的单元测试
Hamcrest 框架的快速介绍,用于为 Java 应用程序编写富有表现力的单元测试用例。
引言
单元测试用例是每个应用程序的重要组成部分。单元测试用例有助于确保代码按预期工作。任何与预期行为的偏差都会在源代码到达生产环境之前被捕获。本文提供关于使用 Hamcrest 框架编写可读、可维护的单元测试的快速介绍。
背景
在 Java 世界中,有很多单元测试框架,其中 Junit 和 TestNG 是最受欢迎的。一般来说,随着测试用例的大小和复杂性增加,单元测试用例开始变得难以阅读和表达。这就是 Hamcrest 可以提供帮助的地方,编写富有表现力的单元测试用例。
Hamcrest 的核心功能
提高测试用例的可读性
Hamcrest 框架中的方法名称的方言/选择提高了测试用例的可读性。
以下是一些示例
// Clearly helps to understand that if actual value is equal to expectedValue
assertThat(actualValue, is(equalTo(expectedValue)));
// Employee object has a property named "firstName"
assertThat(employee, hasProperty("firstName"));
// Marital status should be a value of any of these possible values.
assertThat(employee.getMaritalStatus(), isIn(new String[]{"Married", "Single - Never Married", "Widow", "Divorced"}));
强制类型安全
Hamcrest 方法不允许比较苹果和桔子,而是强制比较苹果和苹果。这在编写测试用例时非常有帮助,因为我们不会比较不匹配的类型。第一层检查是确保我们正在比较相同的对象。Hamcrest 大量使用 Java 泛型来确保类型安全。
以下是一些示例
// Generates compile-time error; isManager returns boolean whereas we are comparing with String.
assertThat(employee.isManager(), is("true"));
支持条件链接
Hamcrest 允许在单个 assertThat
语句中编写多个条件。可以使用像 or/and 这样的逻辑条件将多个条件分组。我们可以编写测试用例,其中条件必须匹配所有条件或必须匹配任何一个条件。以下是一些示例
// Passes only allOf the condition are true; i.e. the firstName is not null, and equal to John
assertThat(employee.getFirstName(), allOf(notNullValue(), is("John")));
支持丰富的匹配器集合
Hamcrest 支持大量已有的匹配器,下面列出了一些
匹配器名称 | 描述 |
allOf | 创建一个匹配器,如果被检查的对象匹配所有指定的匹配器,则该匹配器匹配 |
anyOf | 创建一个匹配器,如果被检查的对象匹配任何指定的匹配器,则该匹配器匹配 |
is | 经常使用的 is(equalTo(x)) 的快捷方式 |
equalTo | 创建一个匹配器,当被检查的对象在逻辑上等于指定的运算数时,通过调用 Object.equals 来匹配 |
not | 创建一个包装现有匹配器的匹配器,但反转其匹配的逻辑 |
notNullValue | 创建一个匹配器,如果被检查的对象不是 null ,则该匹配器匹配 |
nullValue | 创建一个匹配器,如果被检查的对象是 null ,则该匹配器匹配 |
hasProperty | 创建一个匹配器,当被检查的对象具有具有指定名称的 JavaBean 属性时,该匹配器匹配 |
startsWith | 创建一个匹配器,如果被检查的 String 以指定的 String 开头,则该匹配器匹配 |
endsWith | 创建一个匹配器,如果 String 以指定的 String 结尾,则该匹配器匹配 |
高度可扩展
Hamcrest 不仅提供其自身丰富的功能集,而且完全支持创建自定义匹配器。为了编写自定义匹配器,我们需要从 TypeSafeMatcher
基类继承。
以下是从 Hamcrest 文档中摘取的简单示例
//Custom matcher
public class IsNotANumber extends TypeSafeMatcher<Double> {
// Actual comparison. If the number is NaN that means its not a number
@Override
public boolean matchesSafely(Double number) {
return number.isNaN();
}
// Adds custom description.
public void describeTo(Description description) {
description.appendText("This is not a number");
}
// Actual method that is called from the codebase.
@Factory
public static <T> Matcher<Double> notANumber() {
return new IsNotANumber();
}
}
上面创建的自定义匹配器可以这样使用
// The notANumber is a static method available for usage
assertThat(1.0, is(notANumber()));
正如我们所看到的,使用 Hamcrest 构造编写测试用例可以提高测试用例的可读性,使其易于理解和维护。
Maven 依赖
需要将以下 Maven 依赖添加到 pom.xml 以使用 Hamcrest 的所有功能。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
示例测试用例
import com.test.matchers.Employee;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
public class EmployeeTest {
private Employee employee;
@Before
public void initEmployee() {
employee = new Employee();
employee.setFirstName("John");
employee.setDesignation("Manager");
employee.setAge(30);
}
@Test
public void firstNameCannotBeNull() {
assertThat("", employee.getFirstName(), notNullValue());
}
@Test
public void firstNameEqualsJohn() {
assertThat("", employee.getFirstName(), is("John"));
}
@Test
public void designationShouldBeManagerOrContractor() {
assertThat(employee.getDesignation(), anyOf(is("Manager"), is("Contractor")));
}
@Test
public void employeeShouldBeOlderThan20() {
assertThat(employee.getAge(), greaterThan(20L));
}
@Test
public void employeeHasAgeProperty() {
assertThat(employee, hasProperty("age"));
}
}
参考文献
- https://code.google.com/p/hamcrest/
- http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html
历史
- 2014 年 9 月 29 日:初始版本