在Java中支持对象方法契约的Automagic单元测试?

在Java中支持对象方法契约的Automagic单元测试?,java,unit-testing,testing,junit,Java,Unit Testing,Testing,Junit,在开发Java应用程序时,我经常重写对象方法(通常是equals和hashCode)。我想通过某种方式系统地检查我是否遵守了我的每个类的对象方法契约。例如,我希望测试断言对于相等的对象,哈希代码也是相等的。我使用的是JUnit测试框架,所以我更喜欢一些JUnit解决方案,在那里我可以自动生成这些测试,或者一些测试用例,可以访问我的所有类并确保契约得到维护 我使用的是JDK6和JUnit 4.4。只是对这个问题的一些初步想法(这可以解释为什么一个小时后仍然没有答案!) 在实施问题解决方案时,似乎有

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


我使用的是JDK6和JUnit 4.4。

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

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

1/检索我自己的每一个类。很简单,如果您提供一个jar名称,Junit测试初始化方法将:

  • 检查jar是否在JUnit执行类路径中
  • 阅读并加载其中的每个类
  • 仅记忆已声明并重新定义了equals()和hash()的内容(通过反射)
2/测试每个对象
... 这就是问题所在:您必须实例化这些对象,即创建两个实例,并将它们用于equals()测试

这意味着如果你的构造函数取了参数,你必须考虑,

  • 对于基元类型参数(int、boolean、float等)或字符串,限制值的每个组合(对于字符串,“xxx”、“null;fonr int、0、-x、+x、-Integer.MIN、+Integer.MAX等)
  • 对于非本原类型,构建要传递给对象的构造函数的那些实例,以测试(这意味着递归地考虑该参数的构造函数参数:原始类型或不)
最后,并不是所有为这些构造函数自动创建的参数在功能上都有意义,这意味着其中一些值将无法构建实例,因为必须检测断言

然而,这似乎是可能的(如果您愿意,您可以将其设置为),但我希望首先让其他StackOverflow读者对这个问题做出回应,因为他们可能会看到一个比我简单得多的解决方案


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

然后将读取这些注释值,并将根据这些值创建的实例组合起来,以测试equals()。这样就可以把价格降得足够低了

侧节点:通用JUnit测试用例当然会检查每个equals()测试是否有:

  • 如上所述的一些注释(除非只有默认构造函数可用)
  • 相应的hash()方法也被重写(如果没有,if将引发断言异常并在该类上失败)

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

此解决方案的缺点是,您必须使用此测试类“注册”要测试的每个类,并且必须确保向构造函数提供有效的输入,这并不总是容易的。在这种情况下,我对这是否会比手动编写每个类的所有测试工作量大或小持怀疑态度

如果你认为这可行,请投赞成票。如果你想让我更详细地解释它,请留下评论(如果这是一个可行的解决方案,我可能会这样做)

除非你对你的类施加强大的约束,否则这个问题没有“简单”的解决方案

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

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)); } 公共静态无效检查对象身份(对象a1、对象a2、对象b1){ 资产质量(a1、a2); 资产质量(a2,a1); 资产不相同(a1、a2); assertEquals(a1.hashCode(),a2.hashCode()); 资产假(a1等于(b1)); 资产假(a2等于(b1)); 资产假(b1等于(a1)); 资产假(b1等于(a2)); } 用法:

checkObjectIdentity(new Integer(3), new Integer(3), new Integer(4)); checkObjectIdentity(新整数(3)、新整数(3)、新整数(4));
想不出比这更好的了。当您发现bug时,向checkObjectIdentity添加新的调用。

[此处为社区帖子,不涉及任何业力;)]

下面是另一个给你的

Onejava类,实现了一个JUnit测试用例,主方法能够在自己身上启动JUnit

本课程还将:

  • 重写hash()和equals()
  • 定义几个属性(使用基本类型)
  • 定义一个默认构造函数,但也定义一些具有各种参数组合的构造函数
  • 定义一个注释,该注释能够枚举要传递给这些构造函数的“有趣”值
  • 用那些“有趣的”值注释equals()
测试方法接受一个类名参数(这里:它将是它自己),检查具有该名称的类是否有一个带有“有趣的值”注释的equals()重写方法。
如果我
new EqualsTester()
   .addEqualityGroup("hello", "h" + "ello")
   .addEqualityGroup("world", "wor" + "ld")
   .addEqualityGroup(2, 1 + 1)
   .testEquals();