簡體   English   中英

如何斷言List只包含特定類的一個實例?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM