2

我想测试一个列表是否包含一个对象的实例。

例如,对于单个实例:

assertThat(mylist).containsExactly(Matchers.any(ExpectedType.class));

从测试返回的数组obj确实包含一个实例对象ExpectedType。但是我的测试失败了:

java.lang.AssertionError: <[ExpectedType@7c781c42]> 不正确包含 <[an instance of ExpectedType]>。它缺少 <[一个 ExpectedType 的实例]> 并且有意外的项目 <[ExpectedType@7c781c42]>

我该如何编写这个测试?

4

3 回答 3

5

您正在尝试编写一个测试来查看 a 是否List包含使用 HamcrestTruth 的特定类的一个实例。相反,您应该使用HamcrestTruth 编写此测试。Hamcrest 和 Truth 都是使测试更具表现力的库,每个库都有自己特定的用法、样式和语法。如果你愿意的话,你可以在你的测试中同时使用它们,但是在你做的时候把它们的方法链接在一起是行不通的。(也许你很困惑,因为这两个库都可以有以 开头的断言assertThat?)所以对于这个特定的测试,你需要选择其中一个并使用它。

List然而,这两个库都缺乏检查 a是否有一个且只有一个满足条件的项目的内置功能。因此,对于任何一个库,您都有两个选择:您可以对列表进行一些预处理,以便您可以使用内置断言,或者您可以扩展库的语言以赋予它此功能。

以下是一个示例类,它演示了两个库的两个选项:

import com.google.common.collect.FluentIterable;
import com.google.common.truth.*;
import org.hamcrest.*;
import org.junit.Test;

import java.util.*;

import static com.google.common.truth.Truth.assertAbout;
import static com.google.common.truth.Truth.assert_;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

public class ExactlyOneInstanceTest {
    List<Object> myList = Arrays.asList("", 3, 'A', new Object());

    @Test
    public void hamcrestBuiltInTestExactlyOneInstance() {
        long theNumberOfStringsInMyList = myList.stream().filter(o -> o instanceof String).count();
        assertThat(theNumberOfStringsInMyList, equalTo(1L));
    }

    @Test
    public void hamcrestExtendedTestExactlyOneInstance() {
        assertThat(myList, HasExactlyOne.itemThat(is(instanceOf(String.class))));
    }

    @Test
    public void truthBuiltInTestExactlyOneInstance() {
        long theNumberOfStringsInMyList = myList.stream().filter(o -> o instanceof String).count();
        // can't static import Truth.assertThat because of name clash,
        // but we can use this alternative form
        assert_().that(theNumberOfStringsInMyList).isEqualTo(1);
    }

    @Test
    public void truthExtendedTestExactlyOneInstance() {
        assertAbout(iterable()).that(myList).containsExactlyOneInstanceOf(String.class);
    }


    // Hamcrest custom matcher
    static class HasExactlyOne<T> extends TypeSafeDiagnosingMatcher<Iterable<? super T>> {
        Matcher<? super T> elementMatcher;

        HasExactlyOne(Matcher<? super T> elementMatcher) {
            this.elementMatcher = elementMatcher;
        }

        @Factory
        public static <T> Matcher<Iterable<? super T>> itemThat(Matcher<? super T> itemMatcher) {
            return new HasExactlyOne<>(itemMatcher);
        }

        @Override
        public void describeTo(Description description) {
            description
                .appendText("a collection containing exactly one item that ")
                .appendDescriptionOf(elementMatcher);
        }

        @Override
        protected boolean matchesSafely(Iterable<? super T> item, Description mismatchDescription) {
            return FluentIterable.from(item).filter(o -> elementMatcher.matches(o)).size() == 1;
        }
    }

    // Truth custom extension
    static <T> SubjectFactory<ExtendedIterableSubject<T>, Iterable<T>> iterable() {
        return new SubjectFactory<ExtendedIterableSubject<T>, Iterable<T>>() {
            @Override
            public ExtendedIterableSubject<T> getSubject(FailureStrategy fs, Iterable<T> target) {
                return new ExtendedIterableSubject<>(fs, target);
            }
        };
    }

    static class ExtendedIterableSubject<T> extends IterableSubject<ExtendedIterableSubject<T>, T, Iterable<T>> {
        ExtendedIterableSubject(FailureStrategy failureStrategy, Iterable<T> list) {
            super(failureStrategy, list);
        }

        void containsExactlyOneInstanceOf(Class<?> clazz) {
            if (FluentIterable.from(getSubject()).filter(clazz).size() != 1) {
                fail("contains exactly one instance of", clazz.getName());
            }
        }
    }
}

尝试运行并查看该课程并使用对您来说最自然的方式。在编写未来的测试时,只需尝试坚持使用您可用的内置断言,并尝试使@Test方法的意图及其断言立即可读。如果你发现你多次编写相同的代码,或者测试方法不是那么容易阅读,那么重构和/或扩展你正在使用的库的语言。重复,直到所有测试都被测试并且所有测试都易于理解。享受!

于 2015-12-31T07:42:19.233 回答
1

一个更简单的解决方法是

for (Object elt : myList) {
    assertThat(elt).isInstanceOf(ExpectedType.class);
}

heenenee 的回答更优雅:

assertThat(iterable()).that(myList).containsInstancesOf(ExpectedType.class);
于 2016-02-14T15:38:25.253 回答
1

首先,请注意IterableSubject.containsExactly()断言输入“完全包含提供的对象或失败”。这意味着 - 即使您可以Matcher在此处传递对象 - 我们正在断言列表仅包含一个ExpectedType实例。现有答案都没有正确强制执行该不变量(相反,heenenee 的方法断言一个实例ExpectedType 和任意数量的其他实例,并且您的解决方案断言该列表包含任意数量的实例ExpectedType)。当我阅读您的问题时,您确实打算断言完全一致的属性,但无论如何这表明已接受的解决方案存在问题 - 它可能会意外地导致您不打算做出的断言。


当我遇到这样的 Truth API 的限制时,我总是尝试的第一件事就是简单地将断言拆分为单独的步骤。事实证明,这通常易于编写、易于阅读并且通常是防错的。可以理解的是,人们经常试图用 Truth 寻找优雅的单行语句,但一般来说,进行顺序断言并没有错

在这里很难击败该策略:

assertThat(ls).hasSize(1);
assertThat(Iterables.getOnlyElement(ls)).isInstanceOf(String.class);

如果 iterable 的大小不是 1,我们将收到一条错误消息,告诉我们(以及 iterable 的内容)。如果是,我们断言唯一的元素是 的一个实例String。完毕!


对于n实例的一般情况,代码确实会变得有点混乱,但它仍然是合理的。我们只是用来在断言assertWithMessage()中包含有关列表的其他上下文:isInstanceOf()

assertThat(ls).hasSize(n);
for (int i = 0; i < ls.size(); i++) {
  assertWithMessage("list: %s - index: %s", ls, i)
      .that(ls.get(i)).isInstanceOf(String.class);
}

这比实现您自己的 custom 更具可读性和更清晰的正确性Subject


Truth 0.29开始,您可以使用“ Fuzzy Truth ”AKA做得更好Correspondence。这允许您从本质上描述集合的一些转换,然后断言该转换的结果。在这种情况下,我们将创建一个INSTANCEOF_CORRESPONDENCE

private static final Correspondence<Object, Class<?>> INSTANCEOF_CORRESPONDENCE =
    new Correspondence<Object, Class<?>>() {
      @Override
      public boolean compare(@Nullable Object actual, @Nullable Class<?> expected) {
        return expected.isInstance(actual);
      }

      @Override
      public String toString() {
        return "is instanceof";
      }
    };

现在你可以写一个漂亮的单行了!

assertThat(ls).comparingElementsUsing(INSTANCEOF_CORRESPONDENCE)
    .containsExactly(String.class);

这种方法相对于自定义主题的最大好处是它更具可扩展性 - 如果您决定做出不同的断言,则Correspondence实现不需要更改,只需您的断言:

assertThat(ls).comparingElementsUsing(INSTANCEOF_CORRESPONDENCE)
    .doesNotContain(Integer.class);

还有一些暂定计划来支持方法引用和 lambda,.comparingElementsUsing()这样您就可以编写如下内容:

assertThat(ls).comparingElementsUsing((o, c) -> c.isInstance(o), "is instanceof")
    .containsExactly(String.class);
于 2017-06-21T06:53:52.293 回答