J'écris un test simple JUnit pour la classe MyObject
.
Un MyObject
peut être créé à partir d'une méthode de fabrique statique qui prend des varargs de String.
MyObject.ofComponents("Uno", "Dos", "Tres");
À tout moment pendant l'existence de MyObject
, les clients peuvent inspecter les paramètres pour lesquels il a été créé sous la forme d'un Liste <E>, via la méthode .getComponents()
.
myObject.ofComponents(); // -> List<String>: { "Uno", "Dos", "Tres" }
En d'autres termes, un MyObject
mémorise et expose la liste des paramètres qui l'ont créé. Plus de détails sur ce contrat:
getComponents
sera le même que celui choisi pour la création d'objetnull
n'est pas défini (un autre code garantit que null
ne parviendra pas à l'usine)J'écris un test simple qui crée un MyObject
à partir d'une liste de String et vérifie qu'il peut retourne la même liste via .getComponents()
. Je le fais immédiatement mais cela est supposé se produire à distance dans un chemin de code réaliste.
Voici ma tentative:
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));
Iterables.elementsEqual()
le meilleur moyen, à condition que je dispose de la bibliothèque dans mon chemin de construction, comparer ces deux listes? c’est quelque chose qui m’inquiète; devrais-je utiliser cette méthode d'assistance qui passe au-dessus d'un Iterable <E> .. vérifier la taille, puis itérer en cours d'exécution .equals()
.. ou toute autre méthode suggérée par une recherche Internet? quel est le moyen canonique de comparer des listes de tests unitaires?Je préfère utiliser Hamcrest car cela donne un bien meilleur rendement en cas d'échec
Assert.assertThat(listUnderTest,
IsIterableContainingInOrder.contains(expectedList.toArray()));
Au lieu de signaler
expected true, got false
ça va rapporter
expected List containing "1, 2, 3, ..." got list containing "4, 6, 2, ..."
IsIterableContainingInOrder.contain
Selon le Javadoc:
Crée un adapteur pour Iterables qui correspond lorsqu'un passage unique sur l'Iterable examiné produit une série d'éléments, chacun logiquement égal à l'élément correspondant dans les éléments spécifiés. Pour une correspondance positive, l'itéré examiné doit être de la même longueur que le nombre d'éléments spécifiés.
Donc, le listUnderTest
doit avoir le même nombre d'éléments et chaque élément doit correspondre aux valeurs attendues dans l'ordre.
Pourquoi ne pas simplement utiliser List#equals
?
assertEquals(argumentComponents, imapPathComponents);
deux listes sont définies égales si elles contiennent les mêmes éléments dans le même ordre.
La méthode equals () sur votre implémentation List doit faire la comparaison élément par élément, donc
assertEquals(argumentComponents, returnedComponents);
est beaucoup plus facile.
org.junit.Assert.assertEquals()
et org.junit.Assert.assertArrayEquals()
font le travail.
Pour éviter les questions suivantes: Si vous souhaitez ignorer l’ordre, placez tous les éléments à définir, puis comparez: Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))
Cependant, si vous voulez simplement ignorer les doublons mais conserver l'ordre, vous listez avec LinkedHashSet
.
Encore un autre conseil. L'astuce Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))
fonctionne bien jusqu'à ce que la comparaison échoue. Dans ce cas, il affiche un message d'erreur avec des représentations sous forme de chaîne de vos ensembles qui peuvent prêter à confusion parce que l'ordre de l'ensemble n'est presque pas prévisible (du moins pour les objets complexes). Le truc que j’ai trouvé est donc d’envelopper la collection avec un ensemble trié au lieu de HashSet
. Vous pouvez utiliser TreeSet
avec un comparateur personnalisé.
Pour une excellente lisibilité du code, Fest Assertions a le support Nice pour asserting lists
Donc dans ce cas, quelque chose comme:
Assertions.assertThat(returnedComponents).containsExactly("One", "Two", "Three");
Ou faites la liste attendue à un tableau, mais je préfère l'approche ci-dessus parce que c'est plus clair.
Assertions.assertThat(returnedComponents).containsExactly(argumentComponents.toArray());
assertTrue ()/assertFalse (): à utiliser uniquement pour affirmer un résultat booléen renvoyé
assertTrue (Iterables.elementsEqual (argumentComponents, returnComponents));
Vous voulez utiliser Assert.assertTrue()
ou Assert.assertFalse()
comme la méthode à tester renvoie une valeur boolean
.
Lorsque la méthode renvoie un élément spécifique, tel que List
, qui devrait contenir certains éléments attendus, l'assertion avec assertTrue()
de cette façon: Assert.assertTrue(myActualList.containsAll(myExpectedList)
est une anti motif.
Cela rend l'assertion facile à écrire, mais comme le test échoue, il est également difficile de déboguer car le testeur ne vous dira que quelque chose comme:
attendu
true
mais le résultat estfalse
Assert.assertEquals(Object, Object)
dans JUnit4 ou Assertions.assertIterableEquals(Iterable, Iterable)
dans JUnit 5: à utiliser uniquement en tant que equals()
et toString()
sont écrasé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 de test repose sur toString()
des objets comparés.
Comme String
annule equals()
et toString()
, il est parfaitement correct d'affirmer le List<String>
Avec assertEquals(Object,Object)
. Et à propos de cette question: vous devez remplacer equals()
dans une classe, car cela a du sens en termes d'égalité d'objet, pas seulement pour faciliter les assertions dans un test avec JUnit.
Pour faciliter les assertions, vous avez d'autres moyens (que vous pourrez voir dans les points suivants de la réponse).
Est-ce que Guava est un moyen de réaliser/construire des assertions de tests unitaires?
Google Guava Iterables.elementsEqual () constitue-t-il le meilleur moyen de comparer ces deux listes, à condition que la bibliothèque soit dans mon chemin de génération?
Non ce n'est pas. Guava n'est pas une bibliothèque pour écrire des assertions de tests unitaires.
Vous n'en avez pas besoin pour écrire la plupart (tout ce que je pense) des tests unitaires.
Quel est le moyen canonique de comparer des listes pour des tests unitaires?
En tant que bonne pratique, je privilégie les bibliothèques d'assertion/matcher.
Je ne peux pas encourager JUnit à effectuer des assertions spécifiques car cela fournit vraiment trop peu de fonctionnalités et des fonctionnalités limitées: il effectue uniquement une assertion avec un égal égal.
Parfois, vous souhaitez autoriser un ordre quelconque dans les éléments, parfois, vous souhaitez autoriser tout élément de la correspondance attendue avec l'élément réel, et ainsi de suite.
Il est donc judicieux d’utiliser une bibliothèque d’assertions/matcher de tests unitaires telle que Hamcrest ou AssertJ.
La réponse actuelle fournit une solution de Hamcrest. Voici une solution AssertJ .
org.assertj.core.api.ListAssert.containsExactly()
est ce dont vous avez besoin: il vérifie que le groupe actuel contient exactement les valeurs données et rien d'autre, dans l'ordre indiqué:
Vérifie que le groupe actuel contient exactement les valeurs données et rien d'autre, dans l'ordre.
Votre test pourrait ressembler à:
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");
}
Un bon point pour AssertJ est que déclarer List
comme prévu est inutile: cela rend l'assertion plus claire et le code plus lisible:
Assertions.assertThat(myObject.getComponents())
.containsExactly("One", "Two", "Three");
Et si le test échoue:
// Fail : Three was not expected
Assertions.assertThat(myObject.getComponents())
.containsExactly("One", "Two");
vous recevez un message très clair tel que:
Java.lang.AssertionError:
Attendant:
<["Un", "Deux", "Trois"]>
contenir exactement (et dans le même ordre):
<["Un", "Deux"]>
mais certains éléments n'étaient pas attendus:
<["Trois"]>
Les bibliothèques d'assertions/matcher sont indispensables car elles vont vraiment plus loin
Supposons que MyObject
ne stocke pas String
s mais Foo
s des instances telles que:
public class MyFooObject {
private List<Foo> values;
@SafeVarargs
public static MyFooObject ofComponents(Foo... values) {
// ...
}
public List<Foo> getComponents(){
return new ArrayList<>(values);
}
}
C'est un besoin très commun. Avec AssertJ, l'assertion reste 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 les méthodes JUnit exigent que:
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"));
}
Iterables.elementsEqual
est le meilleur choix:Iterables.elementsEqual
est suffisant pour comparer 2 List
s.
Iterables.elementsEqual
est utilisé dans des scénarios plus généraux, il accepte des types plus généraux: Iterable
. Autrement dit, vous pouvez même comparer un List
avec un Set
. (par ordre d'itération, c'est important)
Bien sûr, ArrayList
et LinkedList
définissent assez bien, vous pouvez appeler directement comme égal. Tandis que lorsque vous utilisez une liste non bien définie, Iterables.elementsEqual
est le meilleur choix. Une chose à noter: Iterables.elementsEqual
n'accepte pas null
Pour convertir une liste en tableau: Iterables.toArray
est plus facile.
Pour les tests unitaires, je recommande d’ajouter liste vide à votre scénario de test.