Я повністю згоден з точкою зору ОП. 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 набагато чіткіше та гнучкіше.