[英]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:不是<[ExpectedType @ 7c781c42]>包含<[ExpectedType的實例]>。 它缺少<[ExpectedType的一個實例]>並且有意外的項目<[ExpectedType @ 7c781c42]>
我該怎么寫這個測試?
您正在嘗試編寫一個測試,以查看List
包含使用Hamcrest 和 Truth的特定類的一個實例。 相反,你應該寫這個測試有兩種 Hamcrest 或真理。 Hamcrest和Truth都是使測試更具表現力的庫,每個庫都有自己特定的用法,風格和語法。 如果你願意,你可以在你的測試中一起使用它們,但是當你正在做的時候將它們的方法鏈接在一起是行不通的。 (也許你感到困惑,因為兩個庫都可以有以assertThat
開頭的斷言?)因此,對於這個特定的測試,你需要選擇其中一個並繼續使用它。
但是,這兩個庫都缺少檢查List
是否只有一個滿足條件的項的內置功能。 因此,對於任一庫,您有兩個選擇:要么可以對列表進行一些預處理,以便可以使用內置斷言,要么可以擴展庫的語言以使其具有此功能。
以下是演示兩個庫的兩個選項的示例類:
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
方法的意圖及其斷言立即可讀。 如果您發現多次編寫相同的代碼,或者測試方法不是那么容易閱讀,那么重構和/或擴展您正在使用的庫的語言。 重復,直到測試完所有,所有測試都很容易理解。 請享用!
一個更簡單的解決方法是
for (Object elt : myList) {
assertThat(elt).isInstanceOf(ExpectedType.class);
}
heenenee的回答更優雅:
assertThat(iterable()).that(myList).containsInstancesOf(ExpectedType.class);
首先,請注意IterableSubject.containsExactly()
斷言輸入“ 完全包含提供的對象或失敗。 ”這意味着 - 即使您可以在此處傳遞Matcher
對象 - 我們斷言該列表只包含一個ExpectedType
實例。 這兩個現有的答案都沒有正確地強制執行不變量(而是heenenee的方法斷言ExpectedType
一個實例和任意數量的其他實例 ,並且您的解決方案斷言該列表包含任意數量的ExpectedType
實例)。 當我讀到你的問題時,你確實打算斷言確切的一個屬性,但無論這表明接受的解決方案存在問題 - 它可能會意外地導致你不打算做出的斷言。
當我遇到像這樣的Truth API的限制時,我總是嘗試的第一件事就是將斷言分成單獨的步驟。 這經常被證明易於編寫,易於閱讀,並且通常是防錯的。 可以理解的是,人們經常試圖用真理尋找優雅的單行,但一般來說,進行順序斷言並沒有錯 。
在這里很難擊敗這個策略:
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);
}
這比實現自己的自定義Subject
更具可讀性和更清晰正確。
從真相0.29開始,你可以使用“ 模糊真理 ”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);
還有一些暫定計划用.comparingElementsUsing()
來支持方法引用和lambdas,這樣你就可以編寫如下內容:
assertThat(ls).comparingElementsUsing((o, c) -> c.isInstance(o), "is instanceof")
.containsExactly(String.class);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.