Я повністю згоден з точкою зору ОП. Assert.assertFalse(expected.equals(actual))
не є природним способом виразити нерівність.
Але я заперечую, що крім цього Assert.assertEquals()
, це Assert.assertNotEquals()
працює, але не є зручним для користувача, щоб документувати те, що насправді стверджує тест, і розуміти / налагоджувати, як твердження не вдається.
Так що так, JUnit 4.11 і JUnit 5 надає Assert.assertNotEquals()
( Assertions.assertNotEquals()
у JUnit 5), але я дуже уникаю їх використання.
Як альтернатива, щоб стверджувати стан об'єкта, я, як правило, використовую API відповідності, який легко перекопується в стан об'єкта, який чітко документує наміри тверджень, і це дуже зручно для користувачів, щоб зрозуміти причину відмови твердження.
Ось приклад.
Припустимо, у мене є клас Animal, який я хочу перевірити createWithNewNameAndAge()
методом, методом, який створює новий об'єкт Animal, змінюючи його назву та вік, але зберігаючи улюблену їжу.
Припустимо, я використовую, Assert.assertNotEquals()
щоб стверджувати, що оригінали та нові об'єкти різні.
Ось клас Animal із хибною реалізацією 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+ (або JUnit 5), як тестовий запуск, так і інструмент затвердження
@Test
void assertListNotEquals_JUnit_way() {
Animal scoubi = new Animal("scoubi", 10, "hay");
Animal littleScoubi = scoubi.createWithNewNameAndAge("little scoubi", 1);
Assert.assertNotEquals(scoubi, littleScoubi);
}
Тест не вдався, як очікувалося, але причина, яка надається розробнику, справді не є корисною. Це просто говорить, що значення повинні бути різними і виводити toString()
результат, викликаний фактичним Animal
параметром:
java.lang.AssertionError: Значення повинні бути різними. Актуально: Тварина
[ім'я = scoubi, вік = 10, favoriteFood = сіно]
на org.junit.Assert.fail (Assert.java:88)
Гаразд об'єкти не рівні. Але де проблема?
Яке поле неправильно оцінено в тестованому методі? Один? Два? Усі ?
Щоб виявити це, вам доведеться викопати createWithNewNameAndAge()
налагоджувальний пристрій / використовувати налагоджувач, тоді як тестувальний API був би набагато привітнішим, якби він створив для нас різницю між очікуваним та отриманим.
JUnit 4.11 як тестовий бігун і API тесту Matcher як інструмент затвердження
Тут той же самий сценарій тестування, але для затвердження Animal
стану використовується AssertJ (відмінний API відповідності тесту) :
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");
}
Звичайно, тест все ще не вдається, але цього разу причина чітко вказана:
java.lang.AssertionError:
Очікуємо:
<["scoubi", 10, "сіно"]>
містити точно (і в тому ж порядку):
<["маленький скаубі", 1, "сіно"]>
але деякі елементи не знайдені:
<["маленький скаубі", 1]>
та інших не очікували:
<["scoubi", 10]>
at junit5.MyTest.assertListNotEquals_AssertJ (MyTest.java:26)
Ми можемо прочитати, що для Animal::getName, Animal::getAge, Animal::getFavoriteFood
значень повернутої тварини ми очікуємо, що вони мають таке значення:
"little scoubi", 1, "hay"
але ми мали ці значення:
"scoubi", 10, "hay"
Тож ми знаємо, де розслідуємо: name
і age
не оцінюються правильно. Крім того, факт визначення hay
значення у твердженні Animal::getFavoriteFood()
дозволяє також більш тонко стверджувати повернене Animal
. Ми хочемо, щоб об’єкти не були однаковими для деяких властивостей, але не обов'язково для всіх властивостей.
Отож, безумовно, використання API matcher набагато чіткіше та гнучкіше.