我如何断言列表只包含特定 class 的一个实例?
How do I assert that a List contains exactly one instance of a particular class?
我想测试一个列表是否包含一个对象的实例。
例如,对于单个实例:
assertThat(mylist).containsExactly(Matchers.any(ExpectedType.class));
从测试obj
返回的数组恰好包含实例ExpectedType
的一个对象。
但是我的测试失败了:
java.lang.AssertionError: Not true that <[ExpectedType@7c781c42]> contains exactly <[an instance of ExpectedType]>. It is missing <[an instance of ExpectedType]> and has unexpected items <[ExpectedType@7c781c42]>
如何编写这个测试?
您正在尝试使用 Hamcrest and Truth 编写测试以查看 List
是否恰好包含特定 class 的一个实例。相反,您应该使用 either Hamcrest or Truth 编写此测试。 Hamcrest 和 Truth 都是使测试更具表现力的库,每个库都有自己特定的用法、风格和语法。如果愿意,您可以在测试中将它们并排使用,但是将它们的方法链接在一起是行不通的。 (也许你感到困惑,因为这两个库都可以有以 assertThat
开头的断言?)所以对于这个特定的测试,你需要选择其中一个并使用它。
然而,这两个库都缺少 built-in 检查 List
是否有一个 且只有一个 项目满足条件的功能。因此,对于任何一个库,您都有两个选择:您可以在列表上做一点 pre-processing 以便您可以使用 built-in 断言,或者您可以扩展库的语言以提供它这个功能。
以下示例 class 演示了两个库的两个选项:
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());
}
}
}
}
尝试 运行 并查看 class 并使用对您来说最自然的方式。在编写未来的测试时,只需尝试坚持使用可用的 built-in 断言,并尝试使 @Test
方法的意图及其断言立即可读。如果您发现您多次编写相同的代码,或者某个测试方法不那么容易阅读,那么重构 and/or 扩展您正在使用的库的语言。重复直到所有内容都经过测试并且所有测试都易于理解。享受吧!
一个更简单的解决方法是
for (Object elt : myList) {
assertThat(elt).isInstanceOf(ExpectedType.class);
}
更优雅:
assertThat(iterable()).that(myList).containsInstancesOf(ExpectedType.class);
首先,请注意 IterableSubject.containsExactly()
断言输入“完全包含提供的对象或失败。”这意味着 - 即使您可以通过 Matcher
对象 - 我们断言列表只包含一个 ExpectedType
实例。现有的答案都没有正确地强制执行该不变性(相反,heenenee 的方法断言 ExpectedType
的一个实例和任意数量的其他实例 ,并且您的解决方案断言列表包含 ExpectedType
) 的任意数量 个实例。当我读到你的问题时,你确实打算断言完全一致 属性,但不管这表明公认的解决方案存在问题 - 它可能会意外地导致你不打算做出的断言。
当我 运行 像这样陷入真理的局限性 API 时,我总是尝试的第一件事就是将断言简单地分成单独的步骤。这通常被证明易于编写、易于阅读,并且通常不会出错。可以理解的是,人们经常试图寻找具有 Truth 的优雅单行代码,但一般来说 进行顺序断言并没有错。
这里很难击败该策略:
assertThat(ls).hasSize(1);
assertThat(Iterables.getOnlyElement(ls)).isInstanceOf(String.class);
如果可迭代对象的大小不是 1,我们将收到错误提示(以及可迭代对象的内容)。如果是,我们断言唯一的元素是 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);
}
这比实现您自己的自定义 Subject
.
更易读,也更清楚正确
截至 Truth 0.29 you can do better using "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);
还有 tentative plans 支持方法引用和 lambda 与 .comparingElementsUsing()
这样你就可以编写类似的东西:
assertThat(ls).comparingElementsUsing((o, c) -> c.isInstance(o), "is instanceof")
.containsExactly(String.class);
我想测试一个列表是否包含一个对象的实例。
例如,对于单个实例:
assertThat(mylist).containsExactly(Matchers.any(ExpectedType.class));
从测试obj
返回的数组恰好包含实例ExpectedType
的一个对象。
但是我的测试失败了:
java.lang.AssertionError: Not true that <[ExpectedType@7c781c42]> contains exactly <[an instance of ExpectedType]>. It is missing <[an instance of ExpectedType]> and has unexpected items <[ExpectedType@7c781c42]>
如何编写这个测试?
您正在尝试使用 Hamcrest and Truth 编写测试以查看 List
是否恰好包含特定 class 的一个实例。相反,您应该使用 either Hamcrest or Truth 编写此测试。 Hamcrest 和 Truth 都是使测试更具表现力的库,每个库都有自己特定的用法、风格和语法。如果愿意,您可以在测试中将它们并排使用,但是将它们的方法链接在一起是行不通的。 (也许你感到困惑,因为这两个库都可以有以 assertThat
开头的断言?)所以对于这个特定的测试,你需要选择其中一个并使用它。
然而,这两个库都缺少 built-in 检查 List
是否有一个 且只有一个 项目满足条件的功能。因此,对于任何一个库,您都有两个选择:您可以在列表上做一点 pre-processing 以便您可以使用 built-in 断言,或者您可以扩展库的语言以提供它这个功能。
以下示例 class 演示了两个库的两个选项:
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());
}
}
}
}
尝试 运行 并查看 class 并使用对您来说最自然的方式。在编写未来的测试时,只需尝试坚持使用可用的 built-in 断言,并尝试使 @Test
方法的意图及其断言立即可读。如果您发现您多次编写相同的代码,或者某个测试方法不那么容易阅读,那么重构 and/or 扩展您正在使用的库的语言。重复直到所有内容都经过测试并且所有测试都易于理解。享受吧!
一个更简单的解决方法是
for (Object elt : myList) {
assertThat(elt).isInstanceOf(ExpectedType.class);
}
assertThat(iterable()).that(myList).containsInstancesOf(ExpectedType.class);
首先,请注意 IterableSubject.containsExactly()
断言输入“完全包含提供的对象或失败。”这意味着 - 即使您可以通过 Matcher
对象 - 我们断言列表只包含一个 ExpectedType
实例。现有的答案都没有正确地强制执行该不变性(相反,heenenee 的方法断言 ExpectedType
的一个实例和任意数量的其他实例 ,并且您的解决方案断言列表包含 ExpectedType
) 的任意数量 个实例。当我读到你的问题时,你确实打算断言完全一致 属性,但不管这表明公认的解决方案存在问题 - 它可能会意外地导致你不打算做出的断言。
当我 运行 像这样陷入真理的局限性 API 时,我总是尝试的第一件事就是将断言简单地分成单独的步骤。这通常被证明易于编写、易于阅读,并且通常不会出错。可以理解的是,人们经常试图寻找具有 Truth 的优雅单行代码,但一般来说 进行顺序断言并没有错。
这里很难击败该策略:
assertThat(ls).hasSize(1);
assertThat(Iterables.getOnlyElement(ls)).isInstanceOf(String.class);
如果可迭代对象的大小不是 1,我们将收到错误提示(以及可迭代对象的内容)。如果是,我们断言唯一的元素是 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);
}
这比实现您自己的自定义 Subject
.
截至 Truth 0.29 you can do better using "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);
还有 tentative plans 支持方法引用和 lambda 与 .comparingElementsUsing()
这样你就可以编写类似的东西:
assertThat(ls).comparingElementsUsing((o, c) -> c.isInstance(o), "is instanceof")
.containsExactly(String.class);