Assert égal entre 2 listes dans Junit


165

Comment puis-je faire une assertion d'égalité entre des listes dans un cas de test JUnit ? L'égalité devrait être entre le contenu de la liste.

Par exemple:

List<String> numbers = Arrays.asList("one", "two", "three");
List<String> numbers2 = Arrays.asList("one", "two", "three");
List<String> numbers3 = Arrays.asList("one", "two", "four"); 

// numbers should be equal to numbers2
//numbers should not be equal to numbers3

5
J'aime utiliser de assertArrayEqualsnos jours. Utiliser en combinaison avec List#toArray.
Thibstars

@Thibstars - Je voterais pour cela comme réponse.
dfrankow

2
assertArrayEqualsvous oblige à obtenir des tableaux de vos listes. Vous pouvez opérer directement sur la liste en utilisantassertIterableEquals
ThetaSinner

assertIterableEqualsdisponible pour jUnit5 @ThetaSinner
iec2011007

Réponses:


170

Je me rends compte que cela a été demandé il y a quelques années, cette fonctionnalité n'existait probablement pas à l'époque. Mais maintenant, il est facile de faire ceci:

@Test
public void test_array_pass()
{
  List<String> actual = Arrays.asList("fee", "fi", "foe");
  List<String> expected = Arrays.asList("fee", "fi", "foe");

  assertThat(actual, is(expected));
  assertThat(actual, is(not(expected)));
}

Si vous avez une version récente de Junit installée avec hamcrest, ajoutez simplement ces importations:

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

http://junit.org/junit4/javadoc/latest/org/junit/Assert.html#assertThat(T, org.hamcrest.Matcher)

http://junit.org/junit4/javadoc/latest/org/hamcrest/CoreMatchers.html

http://junit.org/junit4/javadoc/latest/org/hamcrest/core/Is.html


3
System.out.println(actual == expected);retournera faux, mais System.out.println(actual.equals(expected));retournera vrai.
Catfish

@Catfish ouais, c'est déroutant n'est-ce pas. Je pense que je montrais que le matcher utilise .equals(..)au lieu de ==?
djeikyb

2
En quoi est-ce mieux qu'assertEquals?
Raedwald

1
@Raedwald la sortie lorsque l'assertion échoue. J'essaierai de revenir plus tard pour modifier la différence. Les correspondants hamcrest peuvent générer des messages d'échec détaillés. il est possible pour junit de simplement surcharger assertEquals avec une qualité similaire. mais la plupart du temps, junit fournit des fonctionnalités de test unitaire de base, et hamcrest fournit une bibliothèque de descripteurs de différences d'objet agréable à avoir.
djeikyb

29

Ne vous transformez pas en chaîne et ne comparez pas. Ce n'est pas bon pour la performance. Dans le junit, dans Corematchers, il y a un matcher pour ça =>hasItems

List<Integer> yourList = Arrays.asList(1,2,3,4)    
assertThat(yourList, CoreMatchers.hasItems(1,2,3,4,5));

C'est la meilleure façon que je connaisse pour vérifier les éléments d'une liste.


2
Devrait être la réponse choisie, avec une remarque: vous devez également vérifier qu'il n'y a plus d'éléments dans la liste en plus de ce que vous voulez. Peut-être utiliser:Assert.assertEquals(4,yourList.size());
yoni

ou avec une seule ligne:assertThat(yourList.toArray(), arrayContainingInAnyOrder(1,2,3,4,5));
user1778602

3
"Ce n'est pas bon pour la performance." → Je ne sais pas dans quelle mesure il faut prendre en compte les performances lors de l'écriture de tests unitaires ... Mais bien sûr, comparer des chaînes via leur toString()version n'est pas la meilleure façon.
walen

21

Il s'agit d'une réponse héritée, adaptée à JUnit 4.3 et versions antérieures. La version moderne de JUnit inclut un message d'erreur lisible intégré dans la méthode assertThat. Préférez d'autres réponses à cette question, si possible.

List<E> a = resultFromTest();
List<E> expected = Arrays.asList(new E(), new E(), ...);
assertTrue("Expected 'a' and 'expected' to be equal."+
            "\n  'a'        = "+a+
            "\n  'expected' = "+expected, 
            expected.equals(a));

Pour mémoire, comme @Paul l'a mentionné dans son commentaire à cette réponse, deux Lists sont égaux:

si et seulement si l'objet spécifié est également une liste, les deux listes ont la même taille et toutes les paires d'éléments correspondantes dans les deux listes sont égales. (Deux éléments e1et e2sont égaux si (e1==null ? e2==null : e1.equals(e2)).) En d'autres termes, deux listes sont définies comme égales si elles contiennent les mêmes éléments dans le même ordre. Cette définition garantit que la méthode equals fonctionne correctement dans les différentes implémentations de l' Listinterface.

Voir les JavaDocs de l' Listinterface .


1
Donc vous voulez dire que expect.equals (a) se chargera d'affirmer les objets que la liste contient?
Kamal

1
From List javadoc: Compare l'objet spécifié avec cette liste pour l'égalité. Renvoie true si et seulement si l'objet spécifié est également une liste, les deux listes ont la même taille et toutes les paires d'éléments correspondantes dans les deux listes sont égales. (Deux éléments e1 et e2 sont égaux si (e1 == null? E2 == null: e1.equals (e2)).) En d'autres termes, deux listes sont définies égales si elles contiennent les mêmes éléments dans le même ordre . Cette définition garantit que la méthode equals fonctionne correctement dans différentes implémentations de l'interface List.
Paul McKenzie

1
Hélas, ce message d'erreur est loin d'être utile. J'ai trouvé préférable d'écrire une classe utilitaire qui effectue une boucle afin que vous puissiez voir quels éléments sont différents.
Michael Lloyd Lee mlk

@mlk, peut-être, mais j'hésite à écrire une méthode utilitaire personnalisée pour une telle chose. Qu'en est-il du message d'erreur que je viens de modifier?
Bart Kiers

@mlk Je suis d'accord qu'il pourrait être préférable d'écrire une boucle pour tester chaque élément afin que vous sachiez exactement ce qui est différent. Cela dépend des types d'objets figurant dans la liste. Si ce sont des chaînes, alors dites simplement "a =" + a devrait être bien, mais si ce sont des objets complexes (d'autres listes, ou quelque chose qui n'a pas une bonne implémentation toString), il peut être plus facile de les tester individuellement
Matt N

20

assertEquals(Object, Object)de JUnit4 / JUnit 5 ou assertThat(actual, is(expected));de Hamcrest proposé dans les autres réponses ne fonctionneront que comme les deux equals()et toString()sont remplacés pour les classes (et profondément) des objets comparés.

Cela est important car le test d'égalité dans l'assertion s'appuie sur equals()et le message d'échec du test s'appuie sur toString()les objets comparés.
Pour les classes intégrées telles que String, Integeret ainsi de suite ... pas de problème car elles remplacent à la fois equals()et toString(). Il est donc parfaitement valable d'affirmer List<String>ou List<Integer>avec assertEquals(Object,Object).
Et à ce sujet: vous devez surcharger equals()dans une classe parce que cela a du sens en termes d'égalité d'objet, pas seulement pour faciliter les assertions dans un test avec JUnit.
Pour faciliter les affirmations, vous avez d'autres moyens.
En tant que bonne pratique, je préfère les bibliothèques d'assertion / matcher.

Voici une solution AssertJ .

org.assertj.core.api.ListAssert.containsExactly() est ce dont vous avez besoin: il vérifie que le groupe réel contient exactement les valeurs données et rien d'autre, dans l'ordre indiqué dans le javadoc.

Supposons une Fooclasse où vous ajoutez des éléments et où vous pouvez les obtenir.
Un test unitaire de Foocela affirme que les deux listes ont le même contenu pourrait ressembler à ceci:

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

@Test
void add() throws Exception { 
   Foo foo = new Foo();
   foo.add("One", "Two", "Three");
   Assertions.assertThat(foo.getElements())
             .containsExactly("One", "Two", "Three");
}

Un bon point d'AssertJ est que déclarer a Listcomme attendu est inutile: cela rend l'assertion plus droite et le code plus lisible:

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

Mais les bibliothèques d'assertions / matrices sont indispensables car elles vont vraiment plus loin.
Supposons maintenant que Foo cela ne stocke pas les instances Strings mais Bars.
C'est un besoin très courant. Avec AssertJ, l'assertion est encore simple à écrire. Mieux, vous pouvez affirmer que le contenu de la liste est égal même si la classe des éléments ne remplace pas equals()/hashCode()alors que JUnit l'exige:

import org.assertj.core.api.Assertions;
import static org.assertj.core.groups.Tuple.tuple;
import org.junit.jupiter.api.Test;

@Test
void add() throws Exception {
    Foo foo = new Foo();
    foo.add(new Bar(1, "One"), new Bar(2, "Two"), new Bar(3, "Three"));
    Assertions.assertThat(foo.getElements())
              .extracting(Bar::getId, Bar::getName)
              .containsExactly(tuple(1, "One"),
                               tuple(2, "Two"),
                               tuple(3, "Three"));
}

7

Si vous ne vous souciez pas de l'ordre des éléments, je recommande ListAssert.assertEqualsdans junit-addons.

Lien: http://junit-addons.sourceforge.net/

Pour les utilisateurs paresseux de Maven:

    <dependency>
        <groupId>junit-addons</groupId>
        <artifactId>junit-addons</artifactId>
        <version>1.4</version>
        <scope>test</scope>
    </dependency>

7
Remarque: si vous ne vous souciez pas de l'ordre des éléments, vous devez utiliser un ensemble ou une collection, pas une liste.
Barett le

11
Je suis d'accord. Cette bibliothèque est dégoûtante. Pourquoi diable ListAssert.assertEquals () par défaut serait-il sans ordre?
Ryan

5

Vous pouvez utiliser assertEquals dans junit.

import org.junit.Assert;   
import org.junit.Test;

    @Test
    public void test_array_pass()
    {
        List<String> actual = Arrays.asList("fee", "fi", "foe");
        List<String> expected = Arrays.asList("fee", "fi", "foe");
        Assert.assertEquals(actual,expected);
    }

Si l'ordre des éléments est différent, il renverra une erreur.

Si vous déclarez une liste d'objets de modèle, vous devez remplacer la méthode equals dans le modèle spécifique.

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj != null && obj instanceof ModelName) {
            ModelName other = (ModelName) obj;
            return this.getItem().equals(other.getItem()) ;
        }
        return false;
    }

4

si vous ne voulez pas créer une liste de tableaux, vous pouvez également essayer ceci

@Test
public void test_array_pass()
{
  List<String> list = Arrays.asList("fee", "fi", "foe");
  Strint listToString = list.toString();
  Assert.assertTrue(listToString.contains("[fee, fi, foe]"));   // passes  
}

3
List<Integer> figureTypes = new ArrayList<Integer>(
                           Arrays.asList(
                                 1,
                                 2
                            ));

List<Integer> figureTypes2 = new ArrayList<Integer>(
                           Arrays.asList(
                                 1,
                                 2));

assertTrue(figureTypes .equals(figureTypes2 ));

1

Ne réinventez pas la roue!

Il existe une bibliothèque Google Code qui fait cela pour vous: Hamcrest

[Hamcrest] Fournit une bibliothèque d'objets matcher (également appelés contraintes ou prédicats) permettant de définir des règles de «correspondance» de manière déclarative, à utiliser dans d'autres frameworks. Les scénarios typiques incluent des cadres de test, des bibliothèques simulées et des règles de validation d'interface utilisateur.


0

Je sais qu'il existe déjà de nombreuses options pour résoudre ce problème, mais je préférerais faire ce qui suit pour affirmer deux listes dans n'importe quel ordre :

assertTrue(result.containsAll(expected) && expected.containsAll(result))

cela n'échouera-t-il pas si la commande ne correspond pas?
iec2011007

0

assertEquals(expected, result);travaille pour moi. Puisque cette fonction obtient deux objets, vous pouvez lui passer n'importe quoi.

public static void assertEquals(Object expected, Object actual) {
    AssertEquals.assertEquals(expected, actual);
}

-1

Je ne pense pas que toutes les réponses ci-dessus donnent la solution exacte pour comparer deux listes d'objets. La plupart des approches ci-dessus peuvent être utiles pour suivre uniquement la limite des comparaisons - Comparaison de taille - Comparaison de référence

Mais si nous avons des listes d'objets de même taille et des données différentes au niveau des objets, cette approche de comparaison n'aidera pas.

Je pense que l'approche suivante fonctionnera parfaitement avec la substitution d'égaux et de méthode de hashcode sur l'objet défini par l'utilisateur.

J'ai utilisé la lib Xstream pour remplacer les égaux et le hashcode, mais nous pouvons également remplacer les égaux et le hashcode par une logique / comparaison gagnée.

Voici l'exemple pour votre référence

    import com.thoughtworks.xstream.XStream;

    import java.text.ParseException;
    import java.util.ArrayList;
    import java.util.List;

    class TestClass {
      private String name;
      private String id;

      public void setName(String value) {
        this.name = value;
      }

      public String getName() {
        return this.name;
      }

      public String getId() {
        return id;
      }

      public void setId(String id) {
        this.id = id;
      }

      /**
       * @see java.lang.Object#equals(java.lang.Object)
       */
      @Override
      public boolean equals(Object o) {
        XStream xstream = new XStream();
        String oxml = xstream.toXML(o);
        String myxml = xstream.toXML(this);

        return myxml.equals(oxml);
      }

      /**
       * @see java.lang.Object#hashCode()
       */
      @Override
      public int hashCode() {
        XStream xstream = new XStream();
        String myxml = xstream.toXML(this);
        return myxml.hashCode();
      }
    }

    public class XstreamCompareTest {
      public static void main(String[] args) throws ParseException {
      checkObjectEquals();
}

      private static void checkObjectEquals() {
        List<TestClass> testList1 = new ArrayList<TestClass>();
        TestClass tObj1 = new TestClass();
        tObj1.setId("test3");
        tObj1.setName("testname3");
        testList1.add(tObj1);

        TestClass tObj2 = new TestClass();
        tObj2.setId("test2");
        tObj2.setName("testname2");
        testList1.add(tObj2);

        testList1.sort((TestClass t1, TestClass t2) -> t1.getId().compareTo(t2.getId()));

        List<TestClass> testList2 = new ArrayList<TestClass>();
        TestClass tObj3 = new TestClass();
        tObj3.setId("test3");
        tObj3.setName("testname3");
        testList2.add(tObj3);

        TestClass tObj4 = new TestClass();
        tObj4.setId("test2");
        tObj4.setName("testname2");
        testList2.add(tObj4);

        testList2.sort((TestClass t1, TestClass t2) -> t1.getId().compareTo(t2.getId()));

        if (isNotMatch(testList1, testList2)) {
          System.out.println("The list are not matched");
        } else {
          System.out.println("The list are matched");
        }

      }

      private static boolean isNotMatch(List<TestClass> clist1, List<TestClass> clist2) {
        return clist1.size() != clist2.size() || !clist1.equals(clist2);
      }
    }

La chose la plus importante est que vous pouvez ignorer les champs par Annotation (@XStreamOmitField) si vous ne voulez pas inclure de champs sur la vérification d'égalité des objets. Il y a beaucoup d'annotations comme celle-ci à configurer, alors jetez un œil aux annotations de cette bibliothèque.

Je suis sûr que cette réponse vous fera gagner du temps pour identifier la bonne approche pour comparer deux listes d'objets :). Veuillez commenter si vous rencontrez des problèmes à ce sujet.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.