【问题标题】:Overloading static imports重载静态导入
【发布时间】:2024-04-14 07:20:01
【问题描述】:

在测试类中,我想提供我自己的 assertEquals 重载,并提供一些不依赖于 Object.equals 的特殊逻辑。不幸的是,这不起作用,因为一旦我在本地声明了我的 assertEquals 方法,Java 就再也找不到来自 org.junit.Assert.* 的静态导入了。

有没有办法解决这个问题? IE。有没有办法为静态导入的方法提供额外的重载? (相当明显的解决方案是为方法命名不同,但这种解决方案没有相同的审美吸引力。)

我的测试类文件如下所示:

package org.foo.bar;

import static org.junit.Assert.*;

import org.junit.Test;

public class BarTest {
    private static void assertEquals(Bar expected, Bar other) {
        // Some custom logic to test equality.
    }

    @Test
    public void testGetFoo() throws Exception {
        Bar a = new Bar();
        assertEquals(42, a.getFoo()); // Error *
    }

    @Test
    public void testCopyConstructor() throws Exception {
        Bar a = new Bar();
        // Fill a.
        Bar b = new Bar(a);
        assertEquals(a, b);
    }
}

Error * 是“BarTest 类型中的方法assertEquals(Bar, Bar) 不适用于参数(int, int)。”

【问题讨论】:

  • Bar 类中 getFoo() 的返回类型是什么 - int 或其他?
  • 没关系,通过错误消息发现它是 int。
  • 这不是一个直接的答案,但我真的会质疑您这样做的动机,即您所说的“审美吸引力”。如果我是一名阅读单元测试的开发人员,我希望 assertEquals 始终是 JUnit 的实现。在我看来,使用方法 assertBarEquals 更具交流性。这是一个有效的问题,我可以看到它在其他情况下如何有用,我只是不相信任何其他方式能胜过 assertBarEquals()。
  • P.S.问题标题中有一个错字我没有代表来修复,“overloding”->“overloading”。 :)

标签: java static-import


【解决方案1】:

这个答案有两部分——一个是关于编译错误的,另一个是关于 assertEquals() 的用法

问题是在两个不同的命名空间中有两个 assertEquals() 方法 - 一个存在于 org.junit.Assert 命名空间中,另一个存在于 org.foo.bar.BarTest 命名空间(当前命名空间)中。

由于shadowing rules declared in the Java Language Specification,编译器报错。 Assert.assertEquals() 的静态导入被 BarTest 类中声明的 assertEquals() 遮蔽。

解决方法(总是在隐藏声明的情况下)是使用 FQN(完全限定名称)。如果您打算使用 JUnit Assert 类的 assertEquals(...),请使用

org.junit.Assert.assertEquals(...)

当你需要使用你的声明时,只需使用

assertEquals(...)

仅在 BarTest 中,它被遮蔽。在仅需要 Assert.assertEquals() 或 BarTest.asserEquals() 的所有其他类中,您可以导入 Assert 或 BarTest(我认为您不需要在其他地方导入 BarTest,但尽管如此)。

当没有阴影时,您可以简单地导入类或静态方法并在没有 FQN 的情况下使用它。

需要考虑的其他事项

Assert.assertEquals() 在内部利用参数类的 equals() 方法。在测试用例中声明 assertEquals() 违反了 DRY 原则,因为应该一致地实现和使用类型的 equals() 方法 - 在源代码和单元测试中放置两种不同的实现必然会引起混淆。

最好的方法是在 Bar 上实现 equals(),然后在你的测试用例中使用 Assert.assertEquals()。如果您已经拥有,则不需要 BarTest.assertEquals()。 assertEquals() 的伪代码有点像下面

  1. 如果两个参数都为 null,则返回 true。
  2. 如果 expected 不为空,则在 expected 上调用 equals(),并传递 actual 作为参数。如果对象相等,则返回 true。
  3. 如果对象不相等,则抛出带有格式化消息的 AssertionError。

【讨论】:

  • 我真的不认为这违反了 DRY(而是 POLS,因为我们使用的是首字母缩略词):equals 故意不存在于我的类型中,因为它对这些类型绝对没有意义是平等可比的——除了这个单元测试——所以他们没有实现它。
  • 一个棘手的情况,这使得是否具有 equals() 实现或影子 assertEquals() 存在争议。我会选择 equals()。
【解决方案2】:

对于您在单元测试中调用assertEquals(Bar, Bar) 的具体示例,一种可能的解决方案是使用提供静态方法的类来扩展类,如下所示:

class BarAssert extends Assert {
  public static void assertEquals(Bar expected, Bar other) {
        // Some custom logic to test equality.
    }
}

然后您可以包含 import static BarAssert.assertEquals; 并使用您的自定义逻辑。

很抱歉,这并没有直接回答问题,而是更针对您的示例。根据我对该问题的评论,我建议不要使用这种方法。

【讨论】:

  • +1。任何阅读本文并使用此方法的人也应该在失败时抛出 AssertionError。然后,测试运行者可以将失败的测试标记为红色。
  • @Vineet:为什么不直接使用Assert.fail
  • @Konrad,我的错。我忘记了当你子类化时 fail() 可用。它在内部创建并抛出 AssertionError。
【解决方案3】:

唯一的方法是完全限定其中一个。

import static org.junit.Assert.*;

import org.junit.Test;

public class BarTest {

    private static void assertEquals(Bar expected, Bar other) {
        // Some custom logic to test equality.
    }

    @Test
    public void testGetFoo() throws Exception {
        Bar a = new Bar();
        org.junit.Assert.assertEquals(42, a.getFoo());
    }
}

【讨论】:

    【解决方案4】:
    this.assertEquals(a,b);
    

    BarTest.assertEquals(a,b);
    

    我会选择第一个,因为尽管它是一个静态方法,但您必须有一个实例才能使用它(它是私有的)并且this 不会受到未来重命名的奇思妙想.

    【讨论】: