Concordo pienamente con il punto di vista dell'OP. Assert.assertFalse(expected.equals(actual))
non è un modo naturale per esprimere una disuguaglianza.
Ma direi che oltre a ciò funziona Assert.assertEquals()
, Assert.assertNotEquals()
ma non è facile da usare per documentare ciò che il test afferma effettivamente e per capire / eseguire il debug in quanto l'affermazione fallisce.
Quindi sì, JUnit 4.11 e JUnit 5 forniscono Assert.assertNotEquals()
( Assertions.assertNotEquals()
in JUnit 5) ma evito davvero di usarli.
In alternativa, per affermare lo stato di un oggetto, utilizzo generalmente un'API di corrispondenza che scava facilmente nello stato oggetto, documentando chiaramente l'intenzione delle asserzioni e che è molto intuitivo per comprendere la causa dell'errore dell'asserzione.
Ecco un esempio
Supponiamo che io abbia una classe Animal che voglio testare il createWithNewNameAndAge()
metodo, un metodo che crea un nuovo oggetto Animal cambiando il suo nome e la sua età ma mantenendo il suo cibo preferito.
Supponiamo che io Assert.assertNotEquals()
affermi che l'originale e i nuovi oggetti sono diversi.
Ecco la classe Animal con un'implementazione imperfetta di 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+ (o JUnit 5) sia come test runner che come strumento di asserzione
@Test
void assertListNotEquals_JUnit_way() {
Animal scoubi = new Animal("scoubi", 10, "hay");
Animal littleScoubi = scoubi.createWithNewNameAndAge("little scoubi", 1);
Assert.assertNotEquals(scoubi, littleScoubi);
}
Il test ha esito negativo come previsto, ma la causa fornita allo sviluppatore non è davvero utile. Dice solo che i valori dovrebbero essere diversi e produrre il toString()
risultato invocato sul Animal
parametro effettivo :
java.lang.AssertionError: i valori dovrebbero essere diversi. Attuale: animale
[nome = scoubi, età = 10, cibo preferito = fieno]
at org.junit.Assert.fail (Assert.java:88)
Ok gli oggetti non sono uguali. Ma dov'è il problema?
Quale campo non è valutato correttamente nel metodo testato? Uno ? Due ? Tutti loro ?
Per scoprirlo devi scavare createWithNewNameAndAge()
nell'implementazione / usare un debugger mentre l'API di test sarebbe molto più amichevole se ci farebbe il differenziale tra ciò che è previsto e ciò che è ottenuto.
JUnit 4.11 come test runner e un'API Matcher di test come strumento di asserzione
Qui lo stesso scenario di test ma che utilizza AssertJ (un'eccellente API di abbinamento test) per fare l'affermazione dello Animal
stato::
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");
}
Naturalmente il test fallisce ancora, ma questa volta il motivo è chiaramente indicato:
java.lang.AssertionError:
Aspetta:
<["scoubi", 10, "fieno"]>
per contenere esattamente (e nello stesso ordine):
<["little scoubi", 1, "fieno"]>
ma alcuni elementi non sono stati trovati:
<["little scoubi", 1]>
e altri non erano previsti:
<["scoubi", 10]>
at junit5.MyTest.assertListNotEquals_AssertJ (MyTest.java:26)
Possiamo leggere che per i Animal::getName, Animal::getAge, Animal::getFavoriteFood
valori dell'Animale restituito, prevediamo di avere questo valore:
"little scoubi", 1, "hay"
ma abbiamo avuto questi valori:
"scoubi", 10, "hay"
Quindi sappiamo dove indagare: name
e age
non sono valutati correttamente. Inoltre, il fatto di specificare il hay
valore nell'affermazione diAnimal::getFavoriteFood()
consente anche di affermare più finemente il reso Animal
. Vogliamo che gli oggetti non siano gli stessi per alcune proprietà ma non necessariamente per tutte le proprietà.
Quindi sicuramente, usare un'API matcher è molto più chiaro e flessibile.