我完全同意 OP 的观点。 Assert.assertFalse(expected.equals(actual)) 不是表达不平等的自然方式。
但我认为,除了Assert.assertEquals(),Assert.assertNotEquals() 可以工作,但对于记录测试实际断言的内容以及在断言失败时理解/调试不是用户友好的。
所以是的,JUnit 4.11 和 JUnit 5 提供了 Assert.assertNotEquals()(JUnit 5 中的 Assertions.assertNotEquals()),但我真的避免使用它们。
作为替代方案,我通常使用匹配器 API 来断言对象的状态,该 API 可以轻松挖掘对象状态,清楚地记录断言的意图,并且对于理解断言失败的原因非常用户友好。
这是一个例子。
假设我有一个 Animal 类,我想测试 createWithNewNameAndAge() 方法,该方法通过更改名称和年龄但保留它最喜欢的食物来创建一个新的 Animal 对象。
假设我使用Assert.assertNotEquals() 断言原始对象和新对象不同。
下面是 createWithNewNameAndAge() 实现有缺陷的 Animal 类:
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);
}
测试按预期失败,但提供给开发人员的原因确实没有帮助。它只是说值应该不同,并输出在实际 Animal 参数上调用的 toString() 结果:
java.lang.AssertionError:值应该不同。实际:动物
[name=scoubi, age=10, favoriteFood=hay]
在 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:
期待:
完全包含(并且以相同的顺序):
但有些元素没有找到:
和其他人没有预料到:
在 junit5.MyTest.assertListNotEquals_AssertJ(MyTest.java:26)
我们可以看到,对于返回的 Animal 的 Animal::getName, Animal::getAge, Animal::getFavoriteFood 值,我们期望有这些值:
"little scoubi", 1, "hay"
但我们有这些价值观:
"scoubi", 10, "hay"
所以我们知道在哪里进行调查:name 和 age 的值不正确。
此外,在Animal::getFavoriteFood() 的断言中指定hay 值的事实还允许更精细地断言返回的Animal。我们希望某些属性的对象不同,但不一定对每个属性都相同。
所以毫无疑问,使用匹配器 API 更加清晰和灵活。