构建高质量软件:持续集成与持续交付系统实践
上QQ阅读APP看书,第一时间看更新

2.3 自定义对象匹配器

通过前面几节的学习,我们了解到,Hamcrest提供了非常丰富的对象匹配器。大多数时候,这些对象匹配器足以应对日常工作的需要。如果你觉得这些还不能满足自己的要求,则可以通过自定义对象匹配器的方式进行扩展,本节将通过字符串正则匹配的示例来演示如何自定义Hamcrest的对象匹配器。示例代码如程序代码2-7所示。

程序代码2-7 RegexMatcher.java

package com.wangwenjun.cicd.chapter02;

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexMatcher<E extends CharSequence> extends
TypeSafeMatcher<String>
{
    //传入的正则表达式。
    private E expected;

    public RegexMatcher(E expected)
    {
        this.expected = expected;
    }

    //正则表达式既可以是String类型,也可以是StringBuilder/StringBuffer类型。
    @Override
    protected boolean matchesSafely(String item)
    {
        String reg = "";
        if (expected instanceof String)
        {
            reg = (String) expected;
        } else if (expected instanceof StringBuffer)
        {
            reg = ((StringBuffer) expected).toString();
        } else if (expected instanceof StringBuilder)
        {
            reg = ((StringBuilder) expected).toString();
        } else
        {
            return false;
        }

        //进行正则匹配。
        final Pattern pattern = Pattern.compile(reg);
        Matcher matcher = pattern.matcher(item);
        return matcher.matches();
    }

    @Override
    public void describeTo(Description description)
    {
        //期望结果信息描述。
        description.appendText("matched the regex: ").appendValue(expected);
    }

    @Override
    public void describeMismatchSafely(String item,
            Description mismatchDescription)
    {
        //匹配失败时会输出如下描述信息。
        mismatchDescription.appendText("String ")
        .appendValue(item)
        .appendText(" missed match regex: ")
        .appendValue(expected);
    }

    //静态工厂方法。
    public static <T extends CharSequence> RegexMatcher match(T t)
    {
        return new RegexMatcher<T>(t);
    }
}

开发一个对象匹配器还是很容易的,只需要继承TypeSafeMatcher类,重写三个简单方法即可,其中,matchesSafely()方法比较重要,它能直接决定对象匹配器成功与否。下面就来验证我们自定义的对象匹配器,首先,编写一个能够正确运行的匹配断言,代码如下。

@Test
public void testRegexMatcher()
{
    assertThat("test zip code", "123456", match("\\d{6}"));
    assertThat("test zip code", "123456", is(match("\\d{6}")));
    assertThat("test zip code", "123456", not(not(match("\\d{6}"))));
    assertThat("test zip code", "123456", match(new StringBuilder("\\d{6}")));
    assertThat("test ip v4 address", "127.0.0.1", match(
    new StringBuffer("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}")));
}

这里需要注意的一点是,上面的代码不仅可以直接使用match静态方法,而且可以使用Hamcrest提供的语法糖is()方法,其中甚至还使用了双重否定语句not(not(match(...)))。运行单元测试方法,结果没有任何问题,那么匹配失败的情况又是怎样的呢?下面是一个不能正确匹配断言的单元测试方法(请重点关注错误提示信息)。

@Test
public void testRegexMatcherFailed()
{
    //断言失败。
    assertThat("test zip code", "123456", match("\\d{5}"));
}

当断言失败时,RegexMatcher中的describe方法可以在输出中提供详细的错误描述信息,这非常便于定位问题所在,如图2-8所示。

055-01

图2-8 自定义对象匹配器断言失败时的错误提示