hamcrest - сравнить предметы в коллекции

Может ли кто-нибудь объяснить мне, как hamcrest сравнивает коллекции и для чего нужны разные методы из библиотеки?
Я пытаюсь понять, как работает метод IsIterableContainingInAnyOrder#containsInAnyOrder.

В классе IsIterableContainingInAnyOrder есть три перегруженных метода:

  • containsInAnyOrder(T... items)
  • containsInAnyOrder(Matcher<? super T>... itemMatchers)
  • containsInAnyOrder(java.util.Collection<Matcher<? super T>> itemMatchers)

Мой тестовый случай:

import org.hamcrest.Matchers;
import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.assertThat;

public class HamcrestCollections {

    @Test
    public void myTest(){

        List<String> expected = Arrays.asList("one","two","four");

        List<String> actual = Arrays.asList("two","one");

        // This doesn't compile
        assertThat(actual, Matchers.containsInAnyOrder(expected));

        assertThat(actual, Matchers.containsInAnyOrder(expected.toArray()));
    }
}

Первое утверждение не компилируется, ошибка:

Error:(19, 9) java: no suitable method found for assertThat(java.util.List<java.lang.String>,org.hamcrest.Matcher<java.lang.Iterable<? extends java.util.List<java.lang.String>>>)
    method org.junit.Assert.<T>assertThat(java.lang.String,T,org.hamcrest.Matcher<? super T>) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))
    method org.junit.Assert.<T>assertThat(T,org.hamcrest.Matcher<? super T>) is not applicable
      (inferred type does not conform to upper bound(s)
        inferred: java.util.List<java.lang.String>
        upper bound(s): java.lang.Iterable<? extends java.util.List<java.lang.String>>,java.lang.Object)

Я действительно не понимаю, что происходит в этом сообщении.


Я обнаружил, что для работы его нужно преобразовать в массив (второе утверждение в примере):

Matchers.containsInAnyOrder(expected.toArray()));

Я предполагаю, что в данном случае используется этот метод из библиотеки:containsInAnyOrder(T... items), это правда?

Но как можно использовать остальные методы из IsIterableContainingInAnyOrder? Есть ли способ сравнить коллекции без преобразования их в массив?


person krokodilko    schedule 25.02.2018    source источник
comment
Какую версию JUnit и Hamcrest вы используете?   -  person hovanessyan    schedule 27.02.2018
comment
@hovanessyan Я использую Hamcrest 1.3 с JUnit 4.12, и его метод assertThat() работает отлично. В конце концов, он делегирует MatcherAssert.assertThat() Hamcrest. Возможно, это смесь версий библиотеки hamcrest. @krokodilko Это проект maven, можешь поделиться помпоном? В качестве альтернативы вы можете поделиться версиями используемых вами библиотек и, если возможно, путем к классам среды выполнения?   -  person Morfic    schedule 27.02.2018
comment
@Morfic, это действительно зависит от версий JUnit и Hamcrest. В Junit 5 они удалили asserThat из утверждений JUnit в пользу использования непосредственно asserThat Hamcrest - и это не без причины.   -  person hovanessyan    schedule 27.02.2018
comment
@hovanessyan мой комментарий касался вашего первоначального предложения, которое, я думаю, изменилось :-). Хотя я не согласен с тем, что они сделали это по какой-то причине (вероятно, их больше, чем указано ниже), я подозреваю, что они хотели нарушить зависимость JUnit от Hamcrest и разрешить больше сторонних библиотек. Это просто догадка, что может быть путаница с версией Hamcrest, но пока мы не сможем воспроизвести CP, мы не узнаем.   -  person Morfic    schedule 27.02.2018


Ответы (1)


Ваш код прекрасно компилируется в первой форме с JDK 1.8.0_12, Hamcrest 1.3 и JUnit 4.12, хотя он не дает ожидаемого результата из-за подводного камня, который я объясню ниже.

Я могу только догадываться, что у вас может быть смесь версий библиотек, jdk или что-то в этом роде. Однако я считаю, что на самом деле это не имеет значения, из-за того ошибки, о котором я упоминал.


Может ли кто-нибудь объяснить мне, как hamcrest сравнивает коллекции и для чего нужны разные методы из библиотеки?

Короче говоря, вы можете предоставить свой собственный массив/набор сопоставителей или массив элементов, для которых он будет создавать сопоставители. После этого фактические элементы проверяются на соответствие полученному списку соответствий.

Если вы отметите исходники вы увидите, что конструктор IsIterableContainingInAnyOrder принимает набор совпадений:

public IsIterableContainingInAnyOrder(Collection<Matcher<? super T>> matchers) {
    this.matchers = matchers;
}

... в то время как методы, о которых вы спрашивали, являются фабричными методами, которые возвращают экземпляр IsIterableContainingInAnyOrder. Один устарел, и я пропустил его. Затем у нас есть следующие 2, где первый делегирует второму без каких-либо необычных действий:

public static <T> Matcher<Iterable<? extends T>> containsInAnyOrder(Matcher<? super T>... itemMatchers) {
    return containsInAnyOrder(Arrays.asList(itemMatchers));
}

public static <T> Matcher<Iterable<? extends T>> containsInAnyOrder(Collection<Matcher<? super T>> itemMatchers) {
    return new IsIterableContainingInAnyOrder<T>(itemMatchers);
}

... и, наконец, мы имеем:

public static <T> Matcher<Iterable<? extends T>> containsInAnyOrder(T... items) {
    List<Matcher<? super T>> matchers = new ArrayList<Matcher<? super T>>();
    for (T item : items) {
        matchers.add(equalTo(item));
    }

    return new IsIterableContainingInAnyOrder<T>(matchers);
}

Как вы видите, сопоставление создается для каждого элемента, что несколько попался :

  • если вы передадите массив аргументов, вы получите сопоставление для каждого элемента

assertThat(actual, containsInAnyOrder("one", "two", "four")); дает:

java.lang.AssertionError: 
Expected: iterable over ["one", "two", "four"] in any order
     but: No item matches: "four" in ["two", "one"]
  • если вы передадите список, он будет считаться массивом с 1 аргументом, и будет создан только 1 сопоставитель для самого списка

assertThat(actual, containsInAnyOrder(Arrays.asList("one", "two", "four"))) дает:

java.lang.AssertionError: 
Expected: iterable over [<[one, two, four]>] in any order
     but: Not matched: "two"

Заметили тонкую разницу?


Я обнаружил, что для работы его нужно преобразовать в массив (второе утверждение в примере):

Matchers.containsInAnyOrder(expected.toArray())); Я полагаю, что в данном случае используется этот метод из библиотеки:containsInAnyOrder(T... items), это правда?

Но как можно использовать остальные методы из IsIterableContainingInAnyOrder? Есть ли способ сравнить коллекции без преобразования их в массив?

Просто используйте встроенную форму по назначению: assertThat(myList, containsInAnyOrder("one", "two", "four")). Я предполагаю, что это обеспечивает лучшую читабельность и позволяет избежать ненужных переменных или лишнего кода, такого как ожидаемая коллекция. Обычно вам нужно проверить несколько элементов, а не сотни, верно?

person Morfic    schedule 27.02.2018