为啥 JUnit 不提供 assertNotEquals 方法?

Posted

技术标签:

【中文标题】为啥 JUnit 不提供 assertNotEquals 方法?【英文标题】:Why doesn't JUnit provide assertNotEquals methods?为什么 JUnit 不提供 assertNotEquals 方法? 【发布时间】:2010-11-08 23:02:03 【问题描述】:

有人知道为什么 JUnit 4 提供 assertEquals(foo,bar) 而不是 assertNotEqual(foo,bar) 方法吗?

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

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

【问题讨论】:

至少从 JUnit 4.12 开始,提供了 assertNotEquals。 junit.org/junit4/javadoc/4.12/org/junit/… 较新版本的 Junit 提供此功能。这是how to use assertEquals and assertNotEquals 上的一个很好的例子 【参考方案1】:

我也想知道。 Assert 的 API 不是很对称;测试对象是否相同,提供assertSameassertNotSame

当然,写起来也不会太长:

assertFalse(foo.equals(bar));

对于这样的断言,不幸的是,输出的唯一信息部分是测试方法的名称,因此应该单独形成描述性消息:

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

这当然太乏味了,最好自己滚动assertNotEqual。幸运的是,将来它可能会成为 JUnit 的一部分:JUnit issue 22

【讨论】:

但这不太有用,因为 JUnit 无法生成有用的失败消息来告诉您,例如 foo 和 bar 的值不相等。真正的失败原因被隐藏起来,变成了一个简单的布尔值。 我完全同意。尤其是 assertFalse 需要适当的消息参数来产生输出来判断真正出了什么问题。 我认为这对于文本呈现测试很有用。谢谢 文本的问题是它会随着代码的发展而过时。【参考方案2】:

我建议您使用较新的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.*;

【讨论】:

我很欣赏指向备用断言语法的指针,但指向别处并不能回答 为什么 JUnit 从未提供过 assertNotEquals() @seh:我读它的方式不是关于历史兴趣,而是关于在 JUnit 测试中表述“这两个对象不相等”的断言的方法。我这样回答。考虑到“为什么没有assertNotEqual”,我想说这是因为它是一个专门的断言,不像assertEquals 那样经常需要,因此可以通过通用的assertFalse 表达。 “选择你认为最易读的那个”。阅读和编写单元测试的人是程序员。他们真的觉得这比 assertNotEqual(objectUnderTest, someOtherObject) 或 assertFalse(objectUnderTest.equals(someOtherObject)) 更具可读性吗?我不相信花哨的匹配器 API - 程序员探索/发现如何使用它们似乎要困难得多...... @bacar:对于一些断言,这基本上是风格问题。但是assertThat 比有限的assert* 方法集更具表现力。因此,您可以在一行中表达确切的约束,让它(几乎)读起来像一个英文句子并且在断言失败时得到一个有意义的消息。诚然,这并不总是一个杀手级功能,但是当您多次看到它的实际应用时,您就会看到它增加了多少价值。 @Joachim 我同意assertThatassert* 更具表现力,但我不认为它比您可以在assert* 表达式中放入和取出的java 表达式更具表现力一般(毕竟我可以用java代码表达任何东西)。这是我开始使用流式 API 时遇到的一个普遍问题 - 每个基本上都是您必须学习的新 DSL(当我们都已经知道 Java 的时候!)。我想 Hamcrest 现在已经无处不在了,但期望人们知道它是合理的。我会玩...【参考方案3】:

最好将 Hamcrest 用于否定断言而不是 assertFalse,因为在前者中,测试报告将显示断言失败的差异。

如果您使用 assertFalse,您只会在报告中看到断言失败。即丢失有关失败原因的信息。

【讨论】:

【参考方案4】:

我认为缺少 assertNotEqual 确实是一种不对称性,并且使 JUnit 的可学习性降低了一些。请注意,当添加方法会降低 API 的复杂性时,这是一个很好的案例,至少对我而言:对称有助于统治更大的空间。 我的猜测是,遗漏的原因可能是调用该方法的人太少了。然而,我记得有一段时间甚至 assertFalse 都不存在。因此,我对最终可能会添加该方法抱有积极的期望,因为它并不困难;尽管我承认有许多变通方法,甚至是优雅的变通方法。

【讨论】:

【参考方案5】:

我很晚才来参加这个聚会,但我发现表格是:

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

可以用于大多数“不等于”的情况。

int status = doSomething() ; // expected to return 123
assertTrue("doSomething() returned unexpected status", status != 123 ) ;

【讨论】:

虽然这确实有效,但问题是如果断言失败,它只会说“Exepcted true, but was false”,或其他一些不清楚的陈述。如果预期不是 123,而是 123,那就太好了。【参考方案6】:

在 JUnit 4.11 中有一个 assertNotEquals:https://github.com/junit-team/junit/blob/master/doc/ReleaseNotes4.11.md#improvements-to-assert-and-assume

import static org.junit.Assert.assertNotEquals;

【讨论】:

请注意,其中一个 jmock (2.6.0) maven artefacts 泄露了 junit-dep 的旧版本,该版本又具有没有 assertNotEquals 的 Assert 类。最好在使用 ivy 时排除它。 我正在使用 4.12 但仍然无法找到 assertNotEqual。 :s【参考方案7】:

人们想要 assertNotEquals() 的明显原因是比较内置函数,而不必先将它们转换为完整的对象:

详细示例:

....
assertThat(1, not(equalTo(Integer.valueOf(winningBidderId))));
....

对比

assertNotEqual(1, winningBidderId);

遗憾的是,由于默认情况下 Eclipse 不包含 JUnit 4.11,因此您必须是冗长的。

警告我认为“1”不需要包含在 Integer.valueOf() 中,但由于我是从 .NET 新返回的,所以不要指望我的正确性。

【讨论】:

【参考方案8】:

Modulo API 一致性,为什么 JUnit 不提供 assertNotEquals() 与 JUnit 不提供类似方法的原因是一样的

assertStringMatchesTheRegex(regex, str)assertStringDoesntMatchTheRegex(regex, str) assertStringBeginsWith(prefix, str)assertStringDoesntBeginWith(prefix, str)

即为断言逻辑中可能需要的各种东西提供特定的断言方法是没有止境的!

最好提供可组合的测试原语,如 equalTo(...)is(...)not(...)regex(...),并让程序员将它们拼凑在一起,以提高可读性和合理性。

【讨论】:

好吧,由于某种原因,assertEquals() 存在。它没有必要,但它确实如此。问题是关于缺乏对称性 - 为什么 assertEquals 存在但没有它的对应物?【参考方案9】:

我正在使用 jUnit4.12 在 java 8 环境中开发 JUnit

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

所以我把assertNotEquals("addb", string);改成了Assert.assertNotEquals("addb", string);

因此,如果您遇到有关 assertNotEqual 无法识别的问题,请将其更改为 Assert.assertNotEquals(,); 应该可以解决您的问题

【讨论】:

那是因为方法是静态的,必须静态导入。使用这个import static org.junit.Assert.*;,您将能够使用所有没有类名的断言。【参考方案10】:

我完全同意 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"

所以我们知道在哪里进行调查:nameage 的值不正确。 此外,在Animal::getFavoriteFood() 的断言中指定hay 值的事实还允许更精细地断言返回的Animal。我们希望某些属性的对象不同,但不一定对每个属性都相同。 所以毫无疑问,使用匹配器 API 更加清晰和灵活。

【讨论】:

【参考方案11】:

通常当我期望两个对象相等时我会这样做:

assertTrue(obj1.equals(obj2));

和:

assertFalse(obj1.equals(obj2));

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

【讨论】:

以上是关于为啥 JUnit 不提供 assertNotEquals 方法?的主要内容,如果未能解决你的问题,请参考以下文章

Junit使用教程

为啥 Android 上的 JUnit 4 不工作?

为啥伴随对象不使用 JUnit 测试作为私有字段进行测试?

为啥testng依赖junit?

为啥在 Spring Boot 应用程序的 Junit 测试中应用程序属性中的属性不可用?

为啥 JUnit 中的 @Rule 注释字段必须是公开的?