Jak Sprawdzić, że dwie listy zawierają te same elementy w tej samej kolejności?

Kontekst

Piszę prosty JUnit test dla klasy MyObject.

A MyObject można utworzyć z statycznej metody fabrycznej, która przyjmuje varargs String.

MyObject.ofComponents("Uno", "Dos", "Tres");

W dowolnym momencie istnienia MyObject, klient może sprawdzić parametry, przez które został utworzony w postaci lista , metodą .getComponents().

myObject.ofComponents(); // -> List<String>: { "Uno", "Dos", "Tres" }

Innymi słowy, a MyObject obaj pamiętają i ujawnia listę parametrów, które doprowadziły go do istnienia. Więcej szczegółów na temat tej umowy:

  • kolejność getComponents będzie taka sama jak ta wybrana do stworzenia obiektu
  • Duplicate next String komponenty są dozwolone i przechowywane w kolejności
  • zachowanie na null jest niezdefiniowane (inny kod gwarantuje Nie null dostaje się do fabryki)
  • nie ma możliwości zmiany listy komponentów po obiekcie instantiation

Piszę prosty test, który tworzy MyObject z listy String i sprawdza, czy może zwrócić tę samą listę poprzez .getComponents(). robię to natychmiast, ale to powinno się zdarzyć na odległość w realistycznej ścieżce kodu .

Kod

Oto moja próba:


List<String> argumentComponents = Lists.newArrayList("One", "Two", "Three");
List<String> returnedComponents =
    MyObject.ofComponents(
        argumentComponents.toArray(new String[argumentComponents.size()]))
        .getComponents();
assertTrue(Iterables.elementsEqual(argumentComponents, returnedComponents));

Pytanie

  • jest Google Guava Iterables.elementsEqual() najlepszy sposób, pod warunkiem, że posiadam bibliotekę w swojej budowie / align = "left" / to jest coś, co dręczy mnie; powinienem użyć tej metody pomocniczej, która przechodzi przez Iterable.. Sprawdź rozmiar, a następnie wykonaj iterację running .equals().. czy jakakolwiek inna metoda wyszukiwania w Internecie? jaki jest kanoniczny sposób porównywania list do testów jednostkowych?

Opcjonalne spostrzeżenia, które chciałbym uzyskać

  • czy test metody jest zaprojektowany rozsądnie? Nie jestem ekspertem w JUnit!
  • jest .toArray() najlepszym sposobem na konwersję lista do varargsa z E?
Author: Robottinosino, 2012-09-19

7 answers

Wolę używać Hamcrest, ponieważ daje znacznie lepszą wydajność w przypadku awarii

Assert.assertThat(listUnderTest, 
       IsIterableContainingInOrder.contains(expectedList.toArray()));

Zamiast raportowania

expected true, got false

Zgłosi

expected List containing "1, 2, 3, ..." got list containing "4, 6, 2, ..."

/ align = "left" / contain

Hamcrest

Według Javadoc:

Tworzy matcher dla Iterabli, który pasuje, gdy pojedyncze przejście nad badanym Iterablem daje serię przedmiotów, z których każdy jest logicznie równy odpowiadającemu pozycja w podanych pozycjach. Dla pozytywnego dopasowania, badana iterowalna musi mieć taką samą długość jak liczba określonych elementów

Więc {[3] } musi mieć taką samą liczbę elementów i każdy element musi odpowiadać oczekiwanym wartościom w kolejności.

 57
Author: John B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-19 16:47:37

Dlaczego po prostu nie użyć List#equals?

assertEquals(argumentComponents, imapPathComponents);

Umowa o List#equals:

Dwie listy są zdefiniowane jako równe, jeśli zawierają te same elementy w tej samej kolejności.

 76
Author: assylias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-19 13:17:12

Metoda equals () na twojej liście powinna być porównywana elementwise, więc

assertEquals(argumentComponents, returnedComponents);
To o wiele łatwiejsze.
 10
Author: DavW,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-19 13:17:04

org.junit.Assert.assertEquals() i wykonaj swoją pracę.

Aby uniknąć kolejnych pytań: jeśli chcesz zignorować kolejność ustaw wszystkie elementy, a następnie porównaj: Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))

Jeśli jednak chcesz po prostu zignorować duplikaty, ale zachować kolejność zawiń listę za pomocą LinkedHashSet.

Kolejna wskazówka. Sztuczka Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two)) działa dobrze, dopóki porównanie nie zawiedzie. W tym przypadku wyświetla komunikat o błędzie z reprezentacjami ciągów znaków Twoich zestawów, które mogą być mylące, ponieważ kolejność w zestawie jest prawie niemożliwa do przewidzenia (przynajmniej dla obiektów złożonych). Tak więc, sztuczka, którą znalazłem, polega na owinięciu kolekcji za pomocą sortowanego zestawu zamiast HashSet. Możesz użyć TreeSet z niestandardowym komparatorem.
 10
Author: AlexR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-19 13:19:36

Dla doskonałej czytelności kodu, Fest Assertions ma dobre wsparcie dla asserting list

Więc w tym przypadku coś w stylu:

Assertions.assertThat(returnedComponents).containsExactly("One", "Two", "Three");

Lub dodać oczekiwaną listę do tablicy, ale wolę powyższe podejście, ponieważ jest bardziej przejrzyste.

Assertions.assertThat(returnedComponents).containsExactly(argumentComponents.toArray());
 5
Author: crunchdog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-20 08:30:51

AssertTrue () / assertFalse (): aby używać tylko do assert boolean result returned

AssertTrue(Iterables.elementsEqual(argumentprzypisy, returnedComponents));

Chcesz użyć Assert.assertTrue() lub Assert.assertFalse(), ponieważ testowana metoda zwraca wartość boolean.
Ponieważ metoda zwraca konkretną rzecz, taką jak List, która powinna zawierać pewne oczekiwane elementy, twierdząc za pomocą assertTrue() w ten sposób: Assert.assertTrue(myActualList.containsAll(myExpectedList) to anty wzór.
Ułatwia twierdzenie nie jest to jednak możliwe, ponieważ test nie powiódł się, więc trudno jest go debugować, ponieważ test runner powie Ci tylko coś w stylu:

Oczekiwane true ale rzeczywiste to false

Assert.assertEquals(Object, Object) W JUnit4 lub Assertions.assertIterableEquals(Iterable, Iterable) w JUnit 5: aby używać tylko jako equals() i {[16] } są przesłonięte dla klas (i głęboko) porównywanych obiektów

Ma to znaczenie, ponieważ test równości w twierdzeniu opiera się na equals(), a Komunikat o niepowodzeniu testu opiera się na toString() z porównywanych obiektów.
Ponieważ String zastępuje zarówno equals(), jak i toString(), jest całkowicie poprawne twierdzenie List<String> z assertEquals(Object,Object). A co do tej sprawy : musisz nadpisać equals() w klasie, ponieważ ma to sens w kategoriach równości obiektów, nie tylko po to, aby ułatwić twierdzenia w teście z JUnit.
Aby ułatwić twierdzenia, masz inne sposoby (które możesz zobaczyć w kolejnych punktach odpowiedzi).

Czy guawa jest sposobem na wykonywanie / budowanie twierdzeń testów jednostkowych ?

Jest Google Guava Iterables.elementsEqual() najlepszy sposób, pod warunkiem, że mam bibliotekę w mojej ścieżce budowania, aby porównać te dwie listy?

Nie, Nie jest. Guava nie jest biblioteką do pisania twierdzeń testów jednostkowych.
Nie potrzebujesz go, aby napisać większość (wszystko, co myślę) testów jednostkowych.

Jaki jest kanoniczny sposób porównywania list do testów jednostkowych?

Jako dobra praktyka faworyzuję biblioteki assertion/matcher.

Nie mogę zachęcaj JUnit do wykonywania określonych twierdzeń, ponieważ zapewnia to naprawdę zbyt mało i ograniczone funkcje : wykonuje tylko twierdzenie z głęboką równością.
Czasami chcesz zezwolić na dowolną kolejność w elementach, czasami chcesz zezwolić, aby dowolne elementy oczekiwanego dopasowania do rzeczywistego, i tak dla...

Więc użycie biblioteki assertion/matcher testu jednostkowego, takiej jak Hamcrest lub AssertJ, jest właściwym sposobem.
Rzeczywista odpowiedź zapewnia Hamcrest rozwiązanie. Oto AssertJ rozwiązanie.

org.assertj.core.api.ListAssert.containsExactly() jest to, czego potrzebujesz : sprawdza, czy rzeczywista grupa zawiera dokładnie podane wartości i nic więcej, w kolejności jak podano: {]}

Sprawdza, czy rzeczywista grupa zawiera dokładnie podane wartości i nic więcej, w porządku.

Twój test może wyglądać następująco:

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

@Test
void ofComponent_AssertJ() throws Exception {
   MyObject myObject = MyObject.ofComponents("One", "Two", "Three");
   Assertions.assertThat(myObject.getComponents())
             .containsExactly("One", "Two", "Three");
}

Dobrym punktem jest to, że deklarowanie List zgodnie z oczekiwaniami jest niepotrzebne : sprawia, że twierdzenie jest prostsze, a kod bardziej czytelny:

Assertions.assertThat(myObject.getComponents())
         .containsExactly("One", "Two", "Three");

A jeśli test się nie powiedzie:

// Fail : Three was not expected 
Assertions.assertThat(myObject.getComponents())
          .containsExactly("One", "Two");

Otrzymujesz bardzo jasny komunikat, taki jak:

Java.lang.AssertionError:

Zawierać dokładnie (i w tej samej kolejności):

Ale niektórych elementów nie oczekiwano:

Assertion / matcher biblioteki są koniecznością, ponieważ te będą naprawdę dalej

Załóżmy, że MyObject nie przechowuje StringS, ale Foos instancji takich jak:

public class MyFooObject {

    private List<Foo> values;
    @SafeVarargs
    public static MyFooObject ofComponents(Foo... values) {
        // ...
    }

    public List<Foo> getComponents(){
        return new ArrayList<>(values);
    }
}
To bardzo powszechna potrzeba. Z AssertJ twierdzenie jest nadal proste do napisania. Lepiej można stwierdzić, że zawartość listy jest równa, nawet jeśli Klasa elementów nie nadpisuje equals()/hashCode(), podczas gdy JUnit ways wymaga:
import org.assertj.core.api.Assertions;
import static org.assertj.core.groups.Tuple.tuple;
import org.junit.jupiter.api.Test;

@Test
void ofComponent() throws Exception {
    MyFooObject myObject = MyFooObject.ofComponents(new Foo(1, "One"), new Foo(2, "Two"), new Foo(3, "Three"));

    Assertions.assertThat(myObject.getComponents())
              .extracting(Foo::getId, Foo::getName)
              .containsExactly(tuple(1, "One"),
                               tuple(2, "Two"),
                               tuple(3, "Three"));
}
 3
Author: davidxxx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-08 21:33:47
  • moja odpowiedź czy {[0] } jest najlepszym wyborem:

Iterables.elementsEqual wystarczy porównać 2 List s.

Iterables.elementsEqual jest używany w bardziej ogólnych scenariuszach, przyjmuje bardziej ogólne typy: Iterable. Oznacza to, że można nawet porównać List z Set. (według kolejności iteracji, jest to ważne)

Jasne ArrayList i LinkedList definiują równe całkiem dobre, można wywołać równe bezpośrednio. Podczas gdy używasz niezbyt zdefiniowanej listy, Iterables.elementsEqual jest najlepszym wyborem. Jedna rzecz powinna być zauważona: Iterables.elementsEqual nie akceptuje null

  • Konwersja listy do tablicy: Iterables.toArray jest łatwiejsza.

  • W przypadku testu jednostkowego polecam dodać pustą listę do swojego przypadku testowego.

 1
Author: 卢声远 Shengyuan Lu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-19 15:13:57