Hamcrest:如何为匹配器实例化和投射?

时间:2017-07-20 20:01:12

标签: java hamcrest

问题

假设以下简单测试:

sashelp.cars

测试不会编译,因为" moreThan"只能应用于\类型的实例。但我想断言@Test public void test() throws Exception { Object value = 1; assertThat(value, greaterThan(0)); } 是一个大于零的整数。我怎样才能用Hamcrest来表达呢?

到目前为止我尝试了什么:

简单的解决方案是通过强制转换匹配器来删除泛型:

Comparable

可能,但会生成编译器警告并感觉不对。

一个冗长的选择是:

value

感觉"整洁"并且"纠正",但实际上很多代码看起来很简单。我试图在hamcrest找到类似内置的东西,但我没有成功,但也许我错过了什么?

背景

在我的实际测试用例中代码是这样的:

assertThat(value, (Matcher)greaterThan(0));

在问题的简化案例中,我也可以写@Test public void testName() throws Exception { Object value = 1; assertThat(value, instanceOfAnd(Integer.class, greaterThan(0))); } private static<T> Matcher<Object> instanceOfAnd(final Class<T> clazz, final Matcher<? extends T> submatcher) { return new BaseMatcher<Object>() { @Override public boolean matches(final Object item) { return clazz.isInstance(item) && submatcher.matches(clazz.cast(item)); } @Override public void describeTo(final Description description) { description .appendText("is instanceof ") .appendValue(clazz) .appendText(" and ") .appendDescriptionOf(submatcher); } @Override public void describeMismatch(final Object item, final Description description) { if (clazz.isInstance(item)) { submatcher.describeMismatch(item, description); } else { description .appendText("instanceof ") .appendValue(item == null ? null : item.getClass()); } } }; } 。在我的实际情况中,我可以写Map<String, Object> map = executeMethodUnderTest(); assertThat(map, hasEntry(equalTo("the number"), greaterThan(0))); ,但如果出现问题,这当然会使错误信息恶化。

4 个答案:

答案 0 :(得分:5)

这个答案将显示如何使用Hamcrest执行此操作,我不知道是否有比建议更好的方法。

但是,如果您可以包含其他测试库,AssertJ正好支持:

import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class TestClass {

  @Test
  public void test() throws Exception {
    Object value = 1;
    assertThat(value).isInstanceOfSatisfying(Integer.class, integer -> assertThat(integer).isGreaterThan(0));
  }

}

不需要任何演员,AssertJ会为你做这件事。

此外,如果断言失败,它会打印一条漂亮的错误消息,value太小:

java.lang.AssertionError:
Expecting:
 <0>
to be greater than:
 <0> 

或者如果value的类型不正确:

java.lang.AssertionError: 
Expecting:
 <"not an integer">
to be an instance of:
 <java.lang.Integer>
but was instance of:
 <java.lang.String>

可以找到isInstanceOfSatisfying(Class<T> type, Consumer<T> requirements)的Javadoc here,其中还包含一些更复杂的断言示例:

// second constructor parameter is the light saber color
Object yoda = new Jedi("Yoda", "Green");
Object luke = new Jedi("Luke Skywalker", "Green");

Consumer<Jedi> jediRequirements = jedi -> {
  assertThat(jedi.getLightSaberColor()).isEqualTo("Green");
  assertThat(jedi.getName()).doesNotContain("Dark");
};

// assertions succeed:
assertThat(yoda).isInstanceOfSatisfying(Jedi.class, jediRequirements);
assertThat(luke).isInstanceOfSatisfying(Jedi.class, jediRequirements);

// assertions fail:
Jedi vader = new Jedi("Vader", "Red");
assertThat(vader).isInstanceOfSatisfying(Jedi.class, jediRequirements);
// not a Jedi !
assertThat("foo").isInstanceOfSatisfying(Jedi.class, jediRequirements);

答案 1 :(得分:4)

问题是你在这里丢失了类型信息:

sqlite3_exec(dbHandle, "PRAGMA journal_mode=WAL;", 0, 0, 0);

如果你想一想,这是一个非常奇怪的线。这里 Object value = 1; 是最通用的东西,没有可以被合理地告知它,除了可能检查它是value还是检查它的字符串表示是否&# 39;不是。我试图想象一下现代Java中上述行的合法用例,我有点茫然。

明显的解决方法是null

更好的解决方案是投射到assertThat((Comparable)value, greaterThan(0));,因为你要比较一个整数常量;字符串也是可比较的,但仅限于它们之间。

如果您不能认为Integervalue,那么将它与任何内容进行比较都是毫无意义的。如果您的广告投放到Comparable的测试失败,则表示您从其他内容动态投放到Comparable的报告失败了。

答案 2 :(得分:3)

包含Object值的map的问题在于您必须假设要比较的特定类。

缺少的hamcrest是一种将匹配器从给定类型转换为另一种类型的方法,例如这个要点中的一个: https://gist.github.com/dmcg/8ddf275688fd450e977e

public class TransformingMatcher<U, T> extends TypeSafeMatcher<U> {
    private final Matcher<T> base;
    private final Function<? super U, ? extends T> function;

    public TransformingMatcher(Matcher<T> base, Function<? super U, ? extends T> function) {
        this.base = base;
        this.function = function;
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("transformed version of ");
        base.describeTo(description);
    }

    @Override
    protected boolean matchesSafely(U item) {
        return base.matches(function.apply(item));
    }
}

有了这个,你可以这样写你的断言:

@Test
public void testSomething() {
    Map<String, Object> map = new HashMap<>();
    map.put("greater", 5);

    assertThat(map, hasEntry(equalTo("greater"), allOf(instanceOf(Number.class),
            new TransformingMatcher<>(greaterThan((Comparable)0), new Function<Object, Comparable>(){
                @Override
                public Comparable apply(Object input) {
                    return Integer.valueOf(input.toString());
                }
            }))));
}

但问题是,您需要指定一个给定的Comparable数字类(在本例中为Integer)。

如果出现断言错误,则消息为:

java.lang.AssertionError
Expected: map containing ["string"->(an instance of java.lang.Number and transformed version of a value greater than <0>)]
     but: map was [<string=NaN>]

答案 3 :(得分:2)

原始尝试的轻微修改版本如何:

@Test
public void testName() throws Exception {
    Map<String, Object> map = executeMethodUnderTest();

    assertThat(map, hasEntry(equalTo("the number"),
            allOf(instanceOf(Integer.class), integerValue(greaterThan(0)))));
}

private static<T> Matcher<Object> integerValue(final Matcher<T> subMatcher) {
    return new BaseMatcher<Object>() {
        @Override
        public boolean matches(Object item) {
            return subMatcher.matches(Integer.class.cast(item));
        }

        @Override
        public void describeTo(Description description) {
            description.appendDescriptionOf(subMatcher);
        }

        @Override
        public void describeMismatch(Object item, Description description) {
            subMatcher.describeMismatch(item, description);
        }
    };
}

现在自定义匹配器不那么冗长,你仍然可以达到你想要的效果。

如果值太小:

java.lang.AssertionError: 
Expected: map containing ["the number"->(an instance of java.lang.Integer and a value greater than <0>)]
     but: map was [<the number=0>]

如果值错误,请输入:

java.lang.AssertionError: 
Expected: map containing ["the number"->(an instance of java.lang.Integer and a value greater than <0>)]
     but: map was [<the number=something>]