Całkowicie zgadzam się z punktem widzenia PO. Assert.assertFalse(expected.equals(actual))
nie jest naturalnym sposobem wyrażania nierówności.
Ale chciałbym twierdzić, że dalej niż Assert.assertEquals()
, Assert.assertNotEquals()
działa, ale nie jest przyjazny dla dokumentu co test faktycznie dochodzi do zrozumienia i / debug jak nie twierdzenie użytkownika.
Tak więc JUnit 4.11 i JUnit 5 zapewniają Assert.assertNotEquals()
(Assertions.assertNotEquals()
w JUnit 5), ale naprawdę unikam ich używania.
Alternatywnie, aby potwierdzić stan obiektu, ogólnie używam interfejsu API dopasowywania, który łatwo wnika w stan obiektu, który wyraźnie dokumentuje intencję twierdzeń i jest bardzo przyjazny dla użytkownika, aby zrozumieć przyczynę niepowodzenia potwierdzenia.
Oto przykład.
Załóżmy, że mam klasę Animal, którą chcę przetestować, createWithNewNameAndAge()
metodę, która tworzy nowy obiekt Animal, zmieniając jego nazwę i wiek, ale zachowując ulubione jedzenie.
Załóżmy, że używam, Assert.assertNotEquals()
aby stwierdzić, że oryginał i nowe obiekty są różne.
Oto klasa Animal z wadliwą implementacją 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+ (lub JUnit 5) zarówno jako narzędzie do testowania, jak i asercji
@Test
void assertListNotEquals_JUnit_way() {
Animal scoubi = new Animal("scoubi", 10, "hay");
Animal littleScoubi = scoubi.createWithNewNameAndAge("little scoubi", 1);
Assert.assertNotEquals(scoubi, littleScoubi);
}
Test kończy się niepowodzeniem zgodnie z oczekiwaniami, ale przyczyna podana programistowi naprawdę nie jest pomocna. Po prostu mówi, że wartości powinny być różne i wyświetlać toString()
wynik wywołany na rzeczywistym Animal
parametrze:
java.lang.AssertionError: Wartości powinny być inne. Rzeczywiste: zwierzę
[name = scoubi, age = 10, favoriteFood = hay]
at org.junit.Assert.fail (Assert.java:88)
Ok, obiekty nie są równe. Ale gdzie jest problem?
Które pole nie jest poprawnie wycenione w testowanej metodzie? Jeden? Dwa? Wszyscy ?
Aby to odkryć, musisz wykopać createWithNewNameAndAge()
implementację / użyć debuggera, podczas gdy testujący interfejs API byłby o wiele bardziej przyjazny, gdyby uczynił dla nas różnicę między tym, czego się oczekuje, a tym, co zostanie uzyskane.
JUnit 4.11 jako test runner i testowy interfejs API Matchera jako narzędzie do asercji
Tutaj ten sam scenariusz testowy, ale wykorzystujący AssertJ (doskonały interfejs API dopasowywania testów) do potwierdzenia Animal
stanu:
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");
}
Oczywiście test nadal się nie udaje, ale tym razem powód jest jasno określony:
java.lang.AssertionError:
Przy nadziei:
<[„scoubi”, 10, „hay”]>
zawierać dokładnie (i w tej samej kolejności):
<[„little scoubi”, 1, „hay”]>
ale nie znaleziono niektórych elementów:
<[„little scoubi”, 1]>
i innych nie oczekiwano:
<[„scoubi”, 10]>
at junit5.MyTest.assertListNotEquals_AssertJ (MyTest.java:26)
Możemy odczytać, że w przypadku Animal::getName, Animal::getAge, Animal::getFavoriteFood
wartości zwracanego zwierzęcia oczekujemy, że będą miały tę wartość:
"little scoubi", 1, "hay"
ale mieliśmy te wartości:
"scoubi", 10, "hay"
Wiemy więc, gdzie badać: name
i age
nie są odpowiednio wyceniani. Dodatkowo fakt podania hay
wartości w potwierdzeniu Animal::getFavoriteFood()
pozwala również na dokładniejsze potwierdzenie zwróconego Animal
. Chcemy, aby obiekty nie były takie same dla niektórych właściwości, ale niekoniecznie dla wszystkich właściwości.
Zdecydowanie użycie interfejsu API dopasowywania jest o wiele bardziej przejrzyste i elastyczne.