65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Hamcrest 编写富有表现力的单元测试

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2014年9月29日

CPOL

3分钟阅读

viewsIcon

13470

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"));
    }
}

参考文献

历史

  • 2014 年 9 月 29 日:初始版本
© . All rights reserved.