Je suis totalement d'accord avec le point de vue OP. Assert.assertFalse(expected.equals(actual))
n'est pas un moyen naturel d'exprimer une inégalité.
Mais je dirais que plus que Assert.assertEquals()
, les Assert.assertNotEquals()
œuvres , mais n'est pas facile à utiliser pour documenter ce que le test affirme en fait et de comprendre / debug que l'assertion échoue.
Donc oui, JUnit 4.11 et JUnit 5 fournissent Assert.assertNotEquals()
( Assertions.assertNotEquals()
dans JUnit 5) mais j'évite vraiment de les utiliser.
En guise d'alternative, pour affirmer l'état d'un objet, j'utilise généralement une API de correspondance qui creuse facilement dans l'état de l'objet, qui documente clairement l'intention des assertions et qui est très conviviale pour comprendre la cause de l'échec de l'assertion.
Voici un exemple.
Supposons que j'ai une classe Animal dont je veux tester la createWithNewNameAndAge()
méthode, une méthode qui crée un nouvel objet Animal en changeant son nom et son âge mais en gardant sa nourriture préférée.
Supposons que j'utilise Assert.assertNotEquals()
pour affirmer que l'original et les nouveaux objets sont différents.
Voici la classe Animal avec une implémentation imparfaite de createWithNewNameAndAge()
:
public class Animal {
private String name;
private int age;
private String favoriteFood;
public Animal(String name, int age, String favoriteFood) {
this.name = name;
this.age = age;
this.favoriteFood = favoriteFood;
}
// Flawed implementation : use this.name and this.age to create the
// new Animal instead of using the name and age parameters
public Animal createWithNewNameAndAge(String name, int age) {
return new Animal(this.name, this.age, this.favoriteFood);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getFavoriteFood() {
return favoriteFood;
}
@Override
public String toString() {
return "Animal [name=" + name + ", age=" + age + ", favoriteFood=" + favoriteFood + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((favoriteFood == null) ? 0 : favoriteFood.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Animal)) return false;
Animal other = (Animal) obj;
return age == other.age && favoriteFood.equals(other.favoriteFood) &&
name.equals(other.name);
}
}
JUnit 4.11+ (ou JUnit 5) à la fois comme lanceur de test et outil d'assertion
@Test
void assertListNotEquals_JUnit_way() {
Animal scoubi = new Animal("scoubi", 10, "hay");
Animal littleScoubi = scoubi.createWithNewNameAndAge("little scoubi", 1);
Assert.assertNotEquals(scoubi, littleScoubi);
}
Le test échoue comme prévu mais la cause fournie au développeur n'est vraiment pas utile. Il dit simplement que les valeurs doivent être différentes et produire le toString()
résultat appelé sur le Animal
paramètre réel :
java.lang.AssertionError: les valeurs doivent être différentes. Réel: Animal
[name = scoubi, age = 10, favoriteFood = hay]
à org.junit.Assert.fail (Assert.java:88)
Ok les objets ne sont pas égaux. Mais où est le problème?
Quel champ n'est pas correctement évalué dans la méthode testée? Une ? Deux ? Tous ?
Pour le découvrir, vous devez creuser dans l' createWithNewNameAndAge()
implémentation / utiliser un débogueur tandis que l'API de test serait beaucoup plus conviviale si elle faisait pour nous le différentiel entre ce qui est attendu et ce qui est obtenu.
JUnit 4.11 en tant que lanceur de test et une API Matcher de test en tant qu'outil d'assertion
Voici le même scénario de test mais qui utilise AssertJ (une excellente API de test matcher) pour faire l'affirmation de l' Animal
état::
import org.assertj.core.api.Assertions;
@Test
void assertListNotEquals_AssertJ() {
Animal scoubi = new Animal("scoubi", 10, "hay");
Animal littleScoubi = scoubi.createWithNewNameAndAge("little scoubi", 1);
Assertions.assertThat(littleScoubi)
.extracting(Animal::getName, Animal::getAge, Animal::getFavoriteFood)
.containsExactly("little scoubi", 1, "hay");
}
Bien sûr, le test échoue toujours, mais cette fois, la raison est clairement indiquée:
java.lang.AssertionError:
Attendant:
<["scoubi", 10, "foin"]>
contenir exactement (et dans le même ordre):
<["petit scoubi", 1, "foin"]>
mais certains éléments sont introuvables:
<["petit scoubi", 1]>
et d'autres n'étaient pas attendus:
<["scoubi", 10]>
à junit5.MyTest.assertListNotEquals_AssertJ (MyTest.java:26)
Nous pouvons lire que pour les Animal::getName, Animal::getAge, Animal::getFavoriteFood
valeurs de l'animal retourné, nous nous attendons à avoir ces valeurs:
"little scoubi", 1, "hay"
mais nous avons eu ces valeurs:
"scoubi", 10, "hay"
Nous savons donc où enquêter: name
et age
ne sont pas correctement évalués. De plus, le fait de spécifier la hay
valeur dans l'assertion de Animal::getFavoriteFood()
permet également d'affirmer plus finement le retour Animal
. Nous voulons que les objets ne soient pas les mêmes pour certaines propriétés mais pas nécessairement pour toutes les propriétés.
Donc, certainement, l'utilisation d'une API Matcher est beaucoup plus claire et flexible.