Я полностью согласен с точкой зрения ОП. 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: Значения должны быть разными. Актуально: Животное
[name = scoubi, возраст = 10, любимая еда = сено]
в org.junit.Assert.fail (Assert.java:88)
Хорошо, объекты не равны. Но в чем проблема?
Какое поле неправильно оценивается в тестируемом методе? Один ? Два ? Все они ?
Чтобы обнаружить его, вам нужно покопаться в createWithNewNameAndAge()
реализации / использовать отладчик, в то время как API тестирования был бы гораздо более дружественным, если бы он сделал для нас разницу между ожидаемым и полученным.
JUnit 4.11 в качестве тестового прогона и тестовый Matcher API в качестве инструмента подтверждения
Вот тот же сценарий тестирования, но использующий AssertJ (отличный API для проверки соответствия), чтобы установить Animal
состояние:
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, "hay"]>
содержать точно (и в том же порядке):
<["little scoubi", 1, "hay"]>
но некоторые элементы не были найдены:
<["little scoubi", 1]>
и другие не ожидались
<["scoubi", 10]>
at junit5.MyTest.assertListNotEquals_AssertJ (MyTest.java:26)
Мы можем прочитать, что для Animal::getName, Animal::getAge, Animal::getFavoriteFood
значений возвращаемого Animal мы ожидаем получить следующие значения:
"little scoubi", 1, "hay"
но у нас были эти значения:
"scoubi", 10, "hay"
Итак, мы знаем, где исследуем: name
и age
не правильно оценили. Кроме того, факт указания hay
значения в утверждении Animal::getFavoriteFood()
позволяет также более точно утверждать возвращаемое Animal
. Мы хотим, чтобы объекты не были одинаковыми для некоторых свойств, но не обязательно для всех свойств.
Так что, безусловно, использование API-интерфейса для сопоставления является гораздо более понятным и гибким.