为什么JUnit没有提供assertNotEquals方法?

448

有没有人知道为什么JUnit 4提供了assertEquals(foo,bar)但没有提供assertNotEqual(foo,bar)的方法?

它提供了assertNotSame(对应于assertSame)和assertFalse(对应于assertTrue),所以它们没有包含assertNotEqual似乎很奇怪。

顺便说一句,我知道JUnit-addons提供了我要找的方法。 我只是出于好奇而已。


至少从JUnit 4.12开始,提供了assertNotEquals。 https://junit.org/junit4/javadoc/4.12/org/junit/Assert.html#assertNotEquals(double,%20double,%20double) - WebF0x
Junit的新版本提供了此功能。以下链接提供了一个不错的示例,说明如何使用assertEqualsassertNotEquals。 https://www.codingeek.com/tutorials/junit/a-complete-guide-to-junit-with-java-and-gradle/#6_assertions - Hitesh Garg
11个回答

413

我建议您使用更新的assertThat()风格的断言,它可以轻松描述各种否定并自动生成失败时的期望和实际值描述:

assertThat(objectUnderTest, is(not(someOtherObject)));
assertThat(objectUnderTest, not(someOtherObject));
assertThat(objectUnderTest, not(equalTo(someOtherObject)));

这三个选项都是等效的,选择你觉得最易读的那一个。

为了使用方法的简单名称(并允许这种时态语法运行),你需要进行以下导入:

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

142
谢谢您提供有关替代断言语法的指示,但是指向其他地方并不能回答为什么JUnit从未提供assertNotEquals()的问题。 - seh
15
@seh:我理解这个问题不是关于历史兴趣,而是关于在JUnit测试中如何表达“这两个对象不相等”的方式。我回答了这个问题。考虑到“为什么没有assertNotEqual”,我认为这是因为它是一种专门的断言,不像assertEquals那样经常需要,因此可以通过通用的assertFalse来表达。 - Joachim Sauer
22
"选择你认为最易读的内容。阅读和编写单元测试的人都是程序员。他们真的觉得使用这种花哨的匹配器API比assertNotEqual(objectUnderTest, someOtherObject)或assertFalse(objectUnderTest.equals(someOtherObject))更容易阅读吗?我并不被这些花哨的匹配器API所说服 - 程序员似乎要花费更多的精力来探索/发现如何使用它们..." - bacar
5
我同意assertThatassert*更具表现力,但我认为它通常不比你可以放在assert*表达式内外的Java表达式更具表现力(毕竟,我可以用Java代码表达任何东西)。这是我开始遇到流畅式API的一个普遍问题 - 每一个都基本上是一个新的DSL,你必须学习它(当我们已经知道Java语言时!)。我想Hamcrest现在已经足够普及,可以合理地期望人们知道它。我会去试试看... - bacar
以下是具体的导入,如果您不喜欢*导入:import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
- fIwJlxSzApHEZIl
显示剩余2条评论

167

1
请注意,jmock(2.6.0)的maven构件之一泄漏了旧版本的junit-dep,而该版本中的Assert类没有assertNotEquals方法。在使用ivy时最好将其排除。 - gkephorus
9
我正在使用4.12版本,但仍然找不到assertNotEqual。 :s - Mubashar

53
我也有同样的疑问。Assert的API并不是很对称;对于检测对象是否相同,它提供了assertSameassertNotSame
当然,写下以下代码也不会太长:
assertFalse(foo.equals(bar));

使用这样的断言,输出中唯一有用的部分是测试方法的名称,因此需要单独形成描述性消息:

String msg = "Expected <" + foo + "> to be unequal to <" + bar +">";
assertFalse(msg, foo.equals(bar));

当然,那样太繁琐了,更好的方法是自己编写assertNotEqual。幸运的是,在未来,它可能会成为JUnit的一部分:JUnit问题22


21
但这并不是很有用,因为JUnit无法生成有用的失败信息来告诉你,例如foo和bar的值不相等。真正的失败原因被隐藏了,并且变成了一个简单的布尔值。 - Ben James
3
我完全同意。特别是assertFalse需要适当的消息参数才能产生输出,以告诉实际发生了什么错误。 - Mikko Maunu
我认为这对于文本现有测试非常有用。谢谢。 - Marouane Gazanayi
问题在于随着代码的演变,文本将会过时。 - Mark Levison

14

我认为assertNotEqual的缺失确实是一种不对称,并使得JUnit学习起来有些困难。请注意,这是一个很好的例子,添加一个方法可以减少API的复杂性,至少对于我而言:对称性有助于掌控更大的空间。

我猜测忽略该方法的原因可能是要求该方法的人太少了。然而,我记得曾经连assertFalse都不存在的时候;因此,我对该方法最终被添加抱有积极的期望,因为它并不是一个难以实现的方法;尽管我承认有许多解决方法,甚至是优雅的解决方法。


7

我是一个后来者,但我发现这个形式:

static void assertTrue(java.lang.String message, boolean condition) 

可以使大多数“不等于”情况得到解决。
int status = doSomething() ; // expected to return 123
assertTrue("doSomething() returned unexpected status", status != 123 ) ;

5
虽然这样做是可以的,但问题在于如果断言失败,它只会简单地说:“期望为 true,但实际为 false”,或者其他不清楚的表述。更好的方式是将其改为“期望不是 123,但实际为 123”。 - Stealth Rabbi

6

我正在java 8环境下使用jUnit4.12处理JUnit。

对于我来说:即使我使用了
import org.junit.Assert;,编译器也无法找到方法assertNotEquals。

所以我将
assertNotEquals("addb", string);
改为
Assert.assertNotEquals("addb", string);

因此,如果你遇到关于assertNotEqual未被识别的问题,请将其更改为Assert.assertNotEquals(,);它应该解决你的问题。


2
这是因为这些方法是静态的,你必须静态导入它们。使用 import static org.junit.Assert.*;,你就可以在不使用类名的情况下使用所有的断言。 - Tom Stone

3
人们想要使用assertNotEquals()的明显原因是,可以直接比较内置类型而不必先将它们转换为完整的对象:
冗长的示例:
....
assertThat(1, not(equalTo(Integer.valueOf(winningBidderId))));
....

对比。

assertNotEqual(1, winningBidderId);

遗憾的是,由于Eclipse默认不包含JUnit 4.11,所以您必须详细说明。

注意:我认为'1'不需要用Integer.valueOf()包装,但由于我刚从.NET回来,所以请勿指望我的正确性。


2
通常情况下,当我期望两个对象相等时,我会这样做:
assertTrue(obj1.equals(obj2));

并且:

assertFalse(obj1.equals(obj2));

当它们被期望是不相等的时候。我知道这不是你问题的答案,但这是我能得到的最接近的答案。它可以帮助那些在JUnit 4.11之前版本中寻找可以做什么的人。


2

使用Hamcrest来进行负面断言比使用assertFalse更好,因为前者在测试报告中显示故障的区别。

如果您使用assertFalse,您只会在报告中得到一个断言失败。即失败原因丢失了相关信息。


0

我完全同意 OP 的观点。 Assert.assertFalse(expected.equals(actual)) 不是一种自然的表达不等式的方式。
但是,我会认为除了 Assert.assertEquals() 之外,Assert.assertNotEquals() 也可以使用,但不利于记录测试实际断言的内容以及理解/调试,因为断言失败后不容易理解。
所以,是的,JUnit 4.11 和 JUnit 5 提供了 Assert.assertNotEquals()(在 JUnit 5 中是 Assertions.assertNotEquals()),但我真的避免使用它们。

作为替代方案,为了断言对象的状态,我通常会使用一个匹配器 API,它可以轻松地深入对象状态,清楚地记录断言的意图,并且非常用户友好,以便了解断言失败的原因。

这里是一个例子。
假设我有一个 Animal 类,我要测试其 createWithNewNameAndAge() 方法,该方法通过更改其名称和年龄而创建一个新的 Animal 对象,但保留其喜欢的食物。
假设我使用 Assert.assertNotEquals() 来断言原始对象和新对象不同。
这是 Animal 类的一个 flawed 实现的 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);
}

测试结果如预期般失败,但给开发人员的原因并不是很有帮助。它只是说值应该不同,并输出在实际Animal参数上调用的toString()结果:

java.lang.AssertionError: 值应该不同。实际值:Animal

[name=scoubi, age=10, favoriteFood=hay]

at org.junit.Assert.fail(Assert.java:88)

好的,对象确实不相等。但问题出在哪里?
在被测试方法中,哪一个字段的值没有正确设置?一个字段?两个字段?还是全部字段?
要找出答案,你需要深入研究createWithNewNameAndAge()的实现/使用调试器,而如果测试API能为我们区分期望值和实际值,那将更加友好。


JUnit 4.11作为测试运行器,使用测试匹配器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]> 位于junit5.MyTest.assertListNotEquals_AssertJ(MyTest.java:26)
我们可以看到,对于返回的Animal对象的Animal::getName、Animal::getAge和Animal::getFavoriteFood的值,我们期望得到以下结果:
"little scoubi", 1, "hay" 

但我们已经有了这些值:

"scoubi", 10, "hay"

所以我们知道要调查的地方:nameage的值不正确。 此外,在Animal::getFavoriteFood()的断言中指定hay值,也可以更精细地断言返回的Animal。我们希望对象在某些属性上不相同,但不一定在每个属性上都不同。
因此,使用匹配器API明显更清晰、更灵活。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接