用Java维护对象方法契约的自动单元测试?

时间:2008-10-10 03:22:11

标签: java unit-testing testing junit

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

我正在使用JDK6和JUnit 4.4。

8 个答案:

答案 0 :(得分:2)

    public static void checkObjectIdentity(Object a1, Object a2, Object b1) {
        assertEquals(a1, a2);
        assertEquals(a2, a1);
        assertNotSame(a1, a2);
        assertEquals(a1.hashCode(), a2.hashCode());
        assertFalse(a1.equals(b1));
        assertFalse(a2.equals(b1));
        assertFalse(b1.equals(a1));
        assertFalse(b1.equals(a2));
    }

用法:

        checkObjectIdentity(new Integer(3), new Integer(3), new Integer(4));

想不出更好的事情。找到错误时,添加对checkObjectIdentity的新调用。

答案 1 :(得分:1)

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

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

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

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

2 /测试每个物体
......其中有一个问题:你必须实例化那些对象,即创建两个实例,并将它们用于equals()测试。

这意味着如果你的构造函数被认为是参数,你必须考虑,

  • 用于原始类型参数(int,boolean,float,...)或String,每个限制值组合(对于String,“xxx”,“”,null; fonr int,0,-x,+ x , - Integer.MIN,+ Integer.MAX,...等等)
  • 对于非基本类型,构建要传递给要测试的对象的构造函数的实例(意味着你递归地必须考虑该参数的构造函数参数:原始类型与否)

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

但似乎有可能(如果你愿意,你可以将它设为 code-challenge ),但我想首先让其他StackOverflow读者回答这个问题,因为他们可能会看到我更简单的解决方案。


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

然后将读取这些注释值,并将组合这些注释的实例组合在一起以测试equals()。这样可以保持number of combinations足够

Side-node:一个通用的JUnit测试用例当然要检查,对于每个equals()测试,都有:

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

答案 2 :(得分:0)

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

这个解决方案的缺点是你必须“注册”你想用这个测试类测试的每个类,你必须确保给构造函数提供了有效的输入,这并不总是那么容易。从这个角度来看,无论如何,这都比手动编写每个班级的所有测试都更加或更少。

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

答案 3 :(得分:0)

除非您对类强加约束,否则此问题没有“简单”的解决方案。

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

答案 4 :(得分:0)

[社区发布此处,不涉及业力;)

以下是另一个 code-challenge

一个 java类,实现一个JUnit测试用例,主方法能够自行启动JUnit!

本课程还将:

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

测试方法采用类名参数(此处:它本身就是),检查具有该名称的类是否具有带有“有趣值”注释的equals()重写方法。
如果是,它将根据注释构建适当的实例(自身),并测试equals()

这是一个独立的测试类,它定义了一种机制,可以推广到任何具有带注释的重写的equals()函数的类。

请使用JDK6和JUnit4.4

应该将这个类复制粘贴到空java项目的相应包中......然后运行;)


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

  • 是的,测试所需的数据属于要测试的候选类别(即,一个覆盖等于并帮助任何'自动测试者'构建适当的实例)
  • 我没有看到完全作为“测试逻辑”,但作为有用的评论应该做什么等于(顺便说一句,作为上述测试人员要利用的数据;))< / LI>

表示潜在测试数据的注释是否永远不会出现在类中?...嘿,这可能是一个很好的问题:)

答案 5 :(得分:0)

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

换句话说,单元测试的唯一方法可以确保覆盖方法在所有输入上的工作方式相同,因为重写的方法是在所有输入上尝试它;在等于的情况下,这将意味着所有对象状态。

我不确定当前的测试框架是否会自动减少并为您提取可能性。

答案 6 :(得分:0)

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

警告:1720行代码(在findbugs中有0个错误,在“已修改”的checkstyle中为0,对于所有函数都在10以下的圈复杂度。)

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

答案 7 :(得分: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();