【问题标题】:How to test a custom hamcrest matcher?如何测试定制的 hamcrest 匹配器?
【发布时间】:2013-08-21 05:33:04
【问题描述】:

我正在编写一些自定义匹配器来简化 junit 断言。大部分都扩展了TypeSafeMatcher,所以我只需要重写三个方法:

public class NoneConstraintViolationMatcher<T> extends
    TypeSafeMatcher<Set<ConstraintViolation<T>>> {

    @Override
    public void describeTo(Description description) {
        description.appendText("None constraint violations found");
    }

    @Override
    protected void describeMismatchSafely(Set<ConstraintViolation<T>> item,
        Description mismatchDescription) {
        mismatchDescription.
            appendText("Unexpected constraint violation found, but got ");
        mismatchDescription.appendValueList("", ",", "", item);
    }

    @Override
    protected boolean matchesSafely(Set<ConstraintViolation<T>> item) {
        return item.isEmpty();
    }
}

我的问题是如何测试它们?我目前的解决方案是

public class NoneConstraintViolationMatcherUnitTests {

    private NoneConstraintViolationMatcher<Object> matcher = 
        new NoneConstraintViolationMatcher<Object>();

    @Test
    public void returnsMatchedGivenNoneConstraintViolations() throws Excetpion {
         assertTrue(matcher.matches(.....));
    }  

    @Test
    public void returnsMismatchedGivenSomeConstraintViolations() throws Excetpion {
         assertThat(matcher.matches(.....), is(false));
    }        

    @Test
    public void returnsConstraintViolationsFoundWhenMismatched()
        throws Exception {

        StringBuilder out = new StringBuilder();
        //I don't find anything could be used to assert in description

        StringDescription description = new StringDescription(out);

        matcher.describeMismatch(..someCvx, description);

        assertThat(out.toString(), 
            equalTo("Unexpected constraint violation found, but got "));
    }
 }

我想到的另一个解决方案是编写一个 junit 测试并使用 @Rule ExpectedException(handleAssertionError 设置为 true)。

你们如何测试匹配器?提前致谢。

【问题讨论】:

    标签: unit-testing junit matcher hamcrest


    【解决方案1】:

    我正在使用 assertThat 来测试匹配功能。

    @Test
    public void returnsMatchedGivenNoneConstraintViolations() throws Excetpion {
         assertThat(someObject, matcher);
    }  
    
    @Test
    public void returnsMismatchedGivenSomeConstraintViolations() throws Excetpion {
         assertThat(someObject, not(matcher));
    }   
    

    【讨论】:

    • 感谢您的回复。使用这个方案测试描述容易吗?
    【解决方案2】:

    要回答您关于测试描述的后续问题,我认为没有必要这样做。我认为像这样编写returnsConstraintViolationsFoundWhenMismatched 测试就足够了

    @Test(expected=Exception.class)
    public void returnsConstraintViolationsFoundWhenMismatched() {
      // Use the matcher in a way that causes an exception to be thrown
    }
    

    重要的验证是抛出了正确的异常。测试实际消息的内容,不会为您的测试套件增加任何价值。您可以相信 Hamcrest 库对您附加到描述的文本所做的事情是正确的。

    【讨论】:

    • 我有一种情况想测试描述。这是因为我正在检查集合的内容并且只想报告不匹配的内容。更具体地说,我想检查描述的逻辑(例如循环和条件)而不是生成的文本。逻辑应该匹配matchesSafely方法中的逻辑,但是在Java中重用逻辑代码并不是那么简单。
    • 如果matchesSafely的逻辑和描述的生成重叠,你可以在Hamcrest匹配器类中写一个私有方法来重用代码。然后通过测试匹配器,您也在隐式测试生成描述的逻辑。
    • 对不起,当我说逻辑相同时,我指的是条件,而不是循环和条件的主体。例如matchesSafely 可能有if (A) return false; if (B) return false; return true;describeTo 将有if (A) description.appendText("A"); if (B) depscription.appendText("B");。我想模板方法模式适合这里,但它在 Java(Java 8 之前)中非常冗长。
    【解决方案3】:

    我编写单元测试,在断言中调用匹配器的方法。我认为@Factory 是最有帮助的。

    这是匹配器:

    public class ContainsIgnoringCaseMatcher extends TypeSafeMatcher<String> {
    
      private List<String> segmentsToMatch;
    
      public ContainsIgnoringCaseMatcher(String subString, String... additional) {
        Preconditions.checkArgument(subString != null && !subString.isEmpty());
        segmentsToMatch = Lists.newArrayList(subString);
    
        if (additional != null) {
          segmentsToMatch.addAll(Arrays.asList(additional));
        }
      }
    
      @Override
      protected boolean matchesSafely(String fullText) {
        return segmentsToMatch.stream()
            .allMatch(v -> StringUtils.containsIgnoreCase(fullText, v));
    
      }
    
      @Override
      public void describeTo(Description description) {
        description.appendText(String.format("containing each %s", segmentsToMatch));
      }
    
      @Factory
      public static Matcher<String> containsIgnoringCase(String subString, String... additionalStrings) {
        return new ContainsIgnoringCaseMatcher(subString, additionalStrings);
      }
    
      @Factory
      public static Matcher<String> containsIgnoringCase(List<String> subStrings) {
        String first = subStrings.get(0);
    
        if (subStrings.size() > 1) {
          List<String> subList = subStrings.subList(1, subStrings.size());
          String[] additional = subList.toArray(new String[subList.size() - 1]);
    
          return new ContainsIgnoringCaseMatcher(first, additional);
        } else {
          return new ContainsIgnoringCaseMatcher(first);
        }
      }
    }
    

    单元测试方法如下所示:

      @Test
      public void shouldAllowSubstringMatchOfExpected() {
        assertThat("EXPECTED_ITEM", containsIgnoringCase("expected_"));
      }
    
      @Test(expected = AssertionError.class)
      public void shouldNotAllowSubstringMatchOfFound() {
        assertThat("EXPECTED_", containsIgnoringCase("EXPECTED_ITEM"));
      }
    
      @Test
      public void shouldAllowNegatedAssert() {
        assertThat("EXPECTED_", not(containsIgnoringCase("EXPECTED_ITEM")));
      }
    
      @Test(expected = AssertionError.class)
      public void shouldNotAllowMismatch() {
        assertThat("ab", containsIgnoringCase("ba"));
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多