10

在开发 Java 应用程序时,我经常重写 Object 方法(通常是 equals 和 hashCode)。我想要一些方法来系统地检查我是否遵守我的每个类的 Object 方法的合同。例如,我想要断言对于相等的对象,哈希码也相等的测试。我正在使用 JUnit 测试框架,所以最好我想要一些 JUnit 解决方案,我可以在其中自动生成这些测试,或者一些可以以某种方式访问​​我的所有类并确保合同得到遵守的测试用例。

我正在使用 JDK6 和 JUnit 4.4。

4

8 回答 8

2
    公共静态无效checkObjectIdentity(对象a1,对象a2,对象b1){
        断言等于(a1,a2);
        断言等于(a2,a1);
        assertNotSame(a1, a2);
        assertEquals(a1.hashCode(), a2.hashCode());
        断言假(a1.equals(b1));
        断言假(a2.equals(b1));
        断言假(b1.equals(a1));
        断言假(b1.equals(a2));
    }

用法:

        checkObjectIdentity(新整数(3),新整数(3),新整数(4));

想不出更好的了。发现错误时添加对 checkObjectIdentity 的新调用。

于 2008-10-10T12:08:58.700 回答
1

只是对这个问题的一些初步想法(这可以解释为什么整整一个小时后仍然没有答案!?;)

在实施该问题的解决方案时,似乎有两个部分:

1/检索我自己的每一个类。很简单,你给一个 jar 名称,Junit 测试初始化​​方法会:

  • 检查该 jar 是否在 JUnit 执行类路径中
  • 读取并加载其中的每个类
  • 只记住那些已经声明和重新定义了 equals() 和 hash() 的(通过反射)

2/ 测试每一个对象
......这就是问题所在:您必须实例化这些对象,即创建两个实例,并将它们用于 equals() 测试。

这意味着如果你的构造函数被接受参数,你必须考虑,

  • 对于原始类型参数(int、boolean、float、...)或字符串,限制值的每个组合(对于字符串,"xxx"、""、null;fonr int、0、-x、+x、-Integer .MIN, +Integer.MAX, ... 等等)
  • 对于非原始类型,构建要传递给要测试的对象的构造函数的实例(这意味着您必须递归地考虑该参数的构造函数参数:是否为原始类型)

最后,并非为这些构造函数自动创建的每个参数都以功能方式有意义,这意味着其中一些值将无法构建实例,因为必须检测到 Assert:。

然而这似乎是可能的(如果你愿意,你可以把它变成一个代码挑战),但我想首先让其他 StackOverflow 读者回应这个问题,因为他们可能会看到一个比我更简单的解决方案。


为了避免组合问题并使测试相关的测试值接近实际代码本身,我建议定义一个专用注释,其中一个字符串代表构造函数的有效值。将位于您的一个对象的 equals() 覆盖方法的正上方。

然后将读取这些注释值,并组合从这些注释值创建的实例以测试 equals()。这将使组合的数量保持足够低

侧节点:一个通用的 JUnit 测试用例当然会检查,对于每个 equals() 测试,有:

  • 如上所述的一些注释(除非只有默认构造函数可用)
  • 相应的 hash() 方法也被覆盖(如果没有,如果会抛出一个断言异常并在该类上失败)
于 2008-10-10T04:18:15.183 回答
0

这个问题没有“简单”的解决方案,除非你对你的类施加了严格的限制。

例如,如果您为给定类使用多个构造函数,如何确保在 equals/hash 方法中充分考虑所有参数?默认值怎么样?不幸的是,这些东西不能盲目地自动化。

于 2008-10-10T06:02:21.727 回答
0

也许我误解了这个问题(并且过于 CS),但听起来你所描述的问题在一般情况下是不可判定的。

换句话说,单元测试可以确保覆盖方法在所有输入上的工作方式与覆盖方法相同的唯一方法是在所有输入上进行尝试。在 equals 的情况下,这将意味着所有对象状态。

我不确定当前的任何测试框架是否会自动为您缩减和抽象可能性。

于 2008-10-10T15:41:27.820 回答
0

一个老问题的新答案,但在 2011 年 5 月Guava(以前的 Google Collections)发布了一个删除了很多样板的类,称为EqualsTester. 您仍然必须创建自己的实例,但它负责将每个对象与自身、null、平等组中的每个对象、每个其他平等组中的每个对象以及不应匹配的秘密实例进行比较。它还检查所有这些组合的a.equals(b)含义。a.hashCode() == b.hashCode()

来自 Javadoc 的示例:

new EqualsTester()
   .addEqualityGroup("hello", "h" + "ello")
   .addEqualityGroup("world", "wor" + "ld")
   .addEqualityGroup(2, 1 + 1)
   .testEquals();
于 2012-10-31T05:59:57.223 回答
0

[这里的社区帖子,不涉及业力;)]

这是另一个代码挑战

一个java 类,实现了一个 JUnit 测试用例,具有一个能够自行启动 JUnit 的 main 方法!

该课程还将:

  • 覆盖 hash() 和 equals()
  • 定义一些属性(带有原始类型)
  • 定义一个默认构造函数,但也定义了一些具有各种参数组合的构造函数
  • 定义一个能够枚举“有趣”值以传递给那些构造函数的注释
  • 用那些“有趣”的值注释 equals()

测试方法接受一个类名参数(这里:它将是它自己),检查具有该名称的类是否有一个带有“有趣值”注释的 equals() 覆盖方法。
如果是这样,它将根据注释构建(自身)适当的实例,并测试 equals()

这是一个自包含的测试类,它定义了一种机制,该机制能够推广到任何具有带注释的覆盖 equals() 函数的类。

请使用JDK6和JUnit4.4

该类应该被复制粘贴到一个空的java项目的适当包中......然后运行;)


为了增加一些想法,回应尼古拉斯(见评论):

  • 是的,测试所需的数据在要测试的类候选中(即,一个覆盖等于并帮助任何“自动测试器”构建适当实例的数据)
  • 我不认为这完全是“测试逻辑”,而是对应该做的事情的有用评论(顺便说一下,作为上述测试人员要利用的数据;))

代表潜在测试数据的注释应该永远不会出现在课堂上吗?......嘿,这可能是一个很好的问题:)

于 2008-10-10T14:55:51.170 回答
0

我有一个第一个粗略的实现,在这里只使用原始参数使用 Constructor 进行 equals 测试。只需将它复制粘贴到 test.MyClass.java 文件中并运行它。

警告:1720 行代码(findbugs 中的 0 个错误,“修改”检查样式中的 0 个错误,所有函数的圈复杂度低于 10)。

查看所有代码:Auto-test for equals function in java classes through annotations

于 2008-10-19T15:08:45.040 回答
0

我认为 VonC 走在正确的轨道上,但我什至会满足于一些不太复杂的东西,例如接受 .class 对象(正在测试 Object 方法)的参数化测试,然后是可变数量的构造函数参数。然后,您必须使用反射来找到与传入参数的类型匹配的构造函数,并调用构造函数。此测试将假定传递给它的参数将创建对象的有效实例。

这个解决方案的缺点是你必须用这个测试类“注册”你想要测试的每个类,并且你必须确保向构造函数提供有效的输入,这并不总是容易的。有鉴于此,我对这是否比手动编写每个类的所有测试更多或更少的工作持怀疑态度。

如果您认为这可行,请投票...如果您希望我将其更多地刷新,请发表评论(如果事实证明这是一个可行的解决方案,我可能还是会这样做)

于 2008-10-10T04:27:43.243 回答