AssertEquals 2 Listy ignorują kolejność

82

Uważam, że to powinno być naprawdę proste pytanie. Ale jakoś nie mogę znaleźć odpowiedzi w Google.

Załóżmy, że mam 2 listy ciągów. Pierwsza zawiera „Ciąg A” i „Ciąg B” , a druga zawiera „Ciąg B” i „Ciąg A” (zauważ różnicę w kolejności). Chcę je przetestować za pomocą JUnit, aby sprawdzić, czy zawierają dokładnie te same ciągi.

Czy istnieje potwierdzenie, które sprawdza równość ciągów znaków, które ignorują kolejność? Na przykład org.junit.Assert.assertEquals zgłasza AssertionError

java.lang.AssertionError: expected:<[String A, String B]> but was:<[String B, String A]>

Rozwiązaniem jest najpierw posortowanie list, a następnie przekazanie ich do asercji. Ale chcę, aby mój kod był tak prosty i czysty, jak to tylko możliwe.

Używam Hamcrest 1.3 , JUnit 4.11 , Mockito 1.9.5 .

kukis
źródło
3
list1.removeAll(list2)powinien pozostać list1pusty. Myślę, że możesz zbudować na tym, aby uzyskać to, czego chcesz.
SudoRahul
6
containsAlli removeAllO(n²)dla list podczas ich sortowania, a test równości jest O(nlogn). Collections.sort(list1); Collections.sort(list2); assertTrue(list1.equals(list2));jest również czysty.
Alexis C.
1
możliwy duplikat porównania kolekcji Hamcresta
Joe,
@SudoRahul - A co jeśli nie chcesz modyfikować listy, usuwając wszystko?
Erran Morad
@BoratSagdiyev - Ponieważ nie było to ograniczenie ze strony PO, zasugerowałem to. Ale jeśli jest to ograniczenie, to przyjęta odpowiedź na to pytanie rozwiązuje problem.
SudoRahul,

Odpowiedzi:

92

Jak wspomniałeś, że używasz Hamcrest, wybrałbym jedną z kolekcji Matchers

import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import static org.junit.Assert.assertThat;

public class CompareListTest {

    @Test
    public void compareList() {
        List<String> expected = Arrays.asList("String A", "String B");
        List<String> actual = Arrays.asList("String B", "String A");

        assertThat("List equality without order", 
            actual, containsInAnyOrder(expected.toArray()));
    }

}
cheffe
źródło
5
Zobacz także moją odpowiedź stackoverflow.com/a/38262680/297710, która pokazuje, jak ulepszyć dopasowywanie Hamcrest i unikać „.toArray ()” w każdym
asercie
58

Możesz użyć List.containsAll z assertTrue JUnit, aby sprawdzić, czy pierwsza lista zawiera każdy element z drugiego i odwrotnie.

assertTrue(first.size() == second.size() && 
    first.containsAll(second) && second.containsAll(first));
robertoia
źródło
2
@kukis To zależy, czy chcesz sprawdzić duplikaty?
robertoia
4
Tak oczywiście. 2 podane Listy muszą być dokładnie takie same, ignorując kolejność.
kukis
2
@kukis Sprawdź komentarz ZouZou do swojego pytania.
robertoia
1
assertEquals(first.size(), second.size()).. może obejmować .. wtedy powinno działać zgodnie z oczekiwaniami
zdecydowanie nieokreślone
17
To nie działa w przypadku duplikatów na liście. Oto przykład do zademonstrowania: w List<String> list1 = Arrays.asList("a", "a", "b"); List<String> list2 = Arrays.asList("a", "b", "b"); assertEquals(list1.size(), list2.size()); assertTrue(list1.containsAll(list2) && list2.containsAll(list1)); tym przykładzie oba potwierdzenia nie wykrywają, że listy są w rzeczywistości różne. @AlexWorden wspomina o CollectionUtils.isEqualCollection () z Apache Commons Collections, która w tym przykładzie poprawnie wykrywa, że ​​kolekcje nie są równe.
desilvai
11

Oto rozwiązanie, które pozwala uniknąć złożoności kwadratowej (wielokrotne iterowanie po listach). Wykorzystuje klasę Apache Commons CollectionUtils do tworzenia mapy każdego elementu do liczby częstotliwości na liście. Następnie po prostu porównuje dwie mapy.

Assert.assertEquals("Verify same metrics series",
    CollectionUtils.getCardinalityMap(expectedSeriesList),
    CollectionUtils.getCardinalityMap(actualSeriesList));

Właśnie zauważyłem także CollectionUtils.isEqualCollection, która twierdzi, że robi dokładnie to, o co tutaj proszono ...

https://commons.apache.org/proper/commons-collections/apidocs/index.html?org/apache/commons/collections4/CollectionUtils.html

Alex Worden
źródło
4

Z AssertJ, containsExactlyInAnyOrder()czyli containsExactlyInAnyOrderElementsOf()to, czego potrzebujesz:

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

public class CompareListTest {

    @Test
    public void compareListWithTwoVariables() {
        List<String> expected = Arrays.asList("String A", "String B");
        List<String> actual = Arrays.asList("String B", "String A");
        Assertions.assertThat(actual)
                  .containsExactlyInAnyOrderElementsOf(expected);
    }

    @Test
    public void compareListWithInlineExpectedValues() {
        List<String> actual = Arrays.asList("String B", "String A");
        Assertions.assertThat(actual)
                  .containsExactlyInAnyOrder("String A", "String B");
    }    
}
davidxxx
źródło
3
    Collections.sort(excepted);
    Collections.sort(actual);
    assertEquals(excepted,actual);
Tinyfool
źródło
2

Spóźniłem się na imprezę, ale oto moje rozwiązanie wykorzystujące tylko Junit. Wszelkie myśli są mile widziane.

List<String> actual = new ArrayList<>();
actual.add("A");
actual.add("A");
actual.add("B");

List<String> expected = new ArrayList<>();
actual.add("A");
actual.add("B");
actual.add("B");

//Step 1: assert for size
assertEquals(actual.size(), expected.size());

//Step 2: Iterate
for(String e: expected){
    assertTrue(actual.contains(e));
    actual.remove(e);
}
Sujit Joshi
źródło
1

Zauważ, że rozwiązanie Roberta Izquierdo ma ogólnie złożoność kwadratową. Rozwiązanie w HashSets zawsze ma złożoność liniową:

assertTrue(first.size() == second.size() &&
        new HashSet(first).equals(new HashSet(second)));
Leventov
źródło
2
To podejście nie zadziała. Jeśli pierwsza to („Ciąg A”), a druga to („Ciąg A”, „Ciąg A”), nie są to te same listy.
Alexis C.
4
Nie możesz sprawdzić rozmiaru. Jeśli pierwsza to, ("s1", "s2", "s3" ,"s1")a druga to, ("s2", "s1", "s3" ,"s2");nie są tą samą listą.
Alexis C.
@ZouZou przyjęte rozwiązanie ma ten sam problem. Zaproponowałeś jedyne naprawdę poprawne rozwiązanie. Jeśli udzielisz odpowiedzi, zagłosuję za nią.
leventov
@ZouZou Nie są to ta sama lista, ale zawierają dokładnie te same ciągi. OP, wyjaśnij ?. Zrób to również odpowiedź, a ja też zagłosuję :) Nie pomyślałem o tym.
robertoia
2
To nadal nie jest poprawne dla wszystkich przypadków („A”, „A”, „B”) zostanie porównane jako równe („A”, „B”, „B”)
Tim B
1

Aby szybko naprawić, sprawdziłbym oba sposoby:

assertTrue(first.containsAll(second));
assertTrue(second.containsAll(first));

I próbując z sytuacją, w której liczba tych samych elementów jest różna (np. 1, 1, 2 i 1, 2, 2) nie otrzymałem fałszywych alarmów.

Kristjan Veskimäe
źródło
1
Twój kod nadal nie działa. Zobacz ten przykład - @Test public void test1 () {List <String> list1 = Arrays.asList ("a", "a", "b"); List <String> list2 = Arrays.asList ("a", "b", "b"); Assert.assertTrue (list1.containsAll (list2)); Assert.assertTrue (list2.containsAll (list1)); }
Erran Morad,
1

Możesz użyć ListAssert, który znajduje się w słoiku junit -addons.

ListAssert.assertEquals(yourList, Arrays.asList(3, 4, 5));
akungta
źródło
0

Wygląda na to, że inne odpowiedzi odnoszą się do narzędzi innych firm, są nieprawidłowe lub nieefektywne.

Oto rozwiązanie waniliowe O (N) w Javie 8.

public static void assertContainsSame(Collection<?> expected, Collection<?> actual)
{
    assert expected.size() == actual.size();

    Map<Object, Long> counts = expected.stream()
        .collect(Collectors.groupingBy(
                item -> item,
                Collectors.counting()));

    for (Object item : actual)
        assert counts.merge(item, -1L, Long::sum) != -1L;
}
Daniel Avery
źródło