【问题标题】:How should one unit test the hashCode-equals contract?一个单元应该如何测试 hashCode-equals 合约?
【发布时间】:2010-09-16 08:13:14
【问题描述】:

简而言之,hashCode合约,根据Java的object.hashCode():

  1. 除非影响 equals() 的内容发生更改,否则哈希码不应更改
  2. equals() 表示哈希码是 ==

让我们假设主要对不可变数据对象感兴趣 - 它们的信息在构造后永远不会改变,因此假设 #1 成立。剩下#2:问题只是确认equals意味着哈希码==。

显然,我们无法测试所有可能的数据对象,除非该集合非常小。那么,编写可能捕获常见情况的单元测试的最佳方法是什么?

由于此类的实例是不可变的,因此构造此类对象的方法有限;如果可能,此单元测试应涵盖所有这些。在我的脑海中,入口点是子类的构造函数、反序列化和构造函数(应该可以归结为构造函数调用问题)。

[我将尝试通过研究来回答我自己的问题。来自其他 StackOverflowers 的输入是此过程中受欢迎的安全机制。]

[这可能适用于其他 OO 语言,所以我添加了该标签。]

【问题讨论】:

  • 我通常发现由于实现了equals或hashcode,合同已经破裂,但不是两者兼而有之。 openpojo 在 Java 项目中有帮助。 EqualsAndHashCodeMatchRule 对此有所帮助。已经存在的答案提供了有关测试合同其余部分的足够详细信息。

标签: java unit-testing oop


【解决方案1】:

我的建议是想想为什么/如何这可能永远不成立,然后针对这些情况编写一些单元测试。

例如,假设您有一个自定义 Set 类。如果两个集合包含相同的元素,则它们是相等的,但是如果这些元素以不同的顺序存储,则两个相等集合的底层数据结构可能会有所不同。例如:

MySet s1 = new MySet( new String[]{"Hello", "World"} );
MySet s2 = new MySet( new String[]{"World", "Hello"} );
assertEquals(s1, s2);
assertTrue( s1.hashCode()==s2.hashCode() );

在这种情况下,集合中元素的顺序可能会影响它们的哈希,具体取决于您实现的哈希算法。所以这是我要编写的测试类型,因为它测试了我知道某些散列算法可能为我定义为相等的两个对象产生不同结果的情况。

无论是什么,您都应该对自己的自定义类使用类似的标准。

【讨论】:

    【解决方案2】:

    这是我在测试中会有多个断言的唯一情况之一。由于您需要测试 equals 方法,因此您还应该同时检查 hashCode 方法。因此,在您的每个 equals 方法测试用例上,也要检查 hashCode 合同。

    A one = new A(...);
    A two = new A(...);
    assertEquals("These should be equal", one, two);
    int oneCode = one.hashCode();
    assertEquals("HashCodes should be equal", oneCode, two.hashCode());
    assertEquals("HashCode should not change", oneCode, one.hashCode());
    

    当然,检查一个好的 hashCode 是另一个练习。老实说,我不会费心做双重检查以确保 hashCode 在同一次运行中没有改变,通过在代码审查中捕获它并帮助开发人员理解为什么这不是一个好方法来更好地处理这种问题编写 hashCode 方法。

    【讨论】:

      【解决方案3】:

      我会推荐来自 GSBase 的EqualsTester。它基本上可以满足您的需求。不过,我有两个(小)问题:

      • 构造函数完成所有工作,我认为这不是好的做法。
      • 当类 A 的实例等于类 A 的子类的实例时,它会失败。这不一定违反 equals 协定。

      【讨论】:

        【解决方案4】:

        [在撰写本文时,发布了其他三个答案。]

        重申一下,我的问题的目的是找到标准的测试案例,以确认 hashCodeequals 彼此一致。我解决这个问题的方法是想象程序员在编写所讨论的类时所采用的常见路径,即不可变数据。例如:

        1. 写了equals() 而没有写hashCode() 这通常意味着相等被定义为两个实例的字段相等。
        2. 写了hashCode() 而没有写equals()这可能意味着程序员正在寻找更有效的哈希算法。

        在#2 的情况下,我似乎不存在这个问题。没有额外的实例equals(),因此不需要额外的实例具有相同的哈希码。在最坏的情况下,哈希算法可能会对哈希映射产生较差的性能,这超出了本问题的范围。

        在 #1 的情况下,标准单元测试需要使用传递给构造函数的相同数据创建同一对象的两个实例,并验证相等的哈希码。假阳性怎么办?可以选择恰好在一个仍然不健全的算法上产生相同哈希码的构造函数参数。倾向于避免此类参数的单元测试将满足这个问题的精神。这里的捷径是检查equals() 的源代码,认真思考,并在此基础上编写一个测试,但是虽然在某些情况下这可能是必要的,但也可能有一些常见的测试可以捕捉到常见的问题——这样的测试也满足这个问题的精神。

        例如,如果要测试的类(称为 Data)有一个接受 String 的构造函数,并且从 equals() 的 String 构造的实例产生了 equals() 的实例,那么一个好的测试可能会测试:

        • new Data("foo")
        • 另一个new Data("foo")

        我们甚至可以检查new Data(new String("foo")) 的哈希码,以强制不保留字符串,尽管在我看来,这比Data.equals() 更可能产生正确的哈希码是产生正确的结果。

        Eli Courtwright 的回答是根据equals 规范的知识努力思考破解哈希算法的一个例子。一个特殊集合的例子是一个很好的例子,因为用户创建的Collections 有时会出现,并且很容易在哈希算法中出现错误。

        【讨论】:

          【解决方案5】:

          为此使用junit插件是值得的。查看 EqualsHashCodeTestCase http://junit-addons.sourceforge.net/ 类,您可以扩展它并实现 createInstance 和 createNotEqualInstance,这将检查 equals 和 hashCode 方法是否正确。

          【讨论】:

            【解决方案6】:

            EqualsVerifier 是一个相对较新的开源项目,它在测试 equals 合约方面做得非常好。它没有来自 GSBase 的 EqualsTester 的 issues。我肯定会推荐它。

            【讨论】:

            • 只是使用 EqualsVerifier 教会了我一些关于 Java 的知识!
            • 我疯了吗? EqualsVerifier 似乎根本没有检查值对象语义!它认为我的类正确实现了 equals(),但我的类使用默认的“==”行为。怎么会这样?!我一定错过了一些简单的东西。
            • 啊哈!如果我不覆盖 equals(),那么 EqualsVerifier 会假定一切正常!?这不是我所期望的。我希望不覆盖 equals() 的行为与覆盖 equals() 的行为相同,以执行与 super.equals() 完全相同的操作。很奇怪。
            • @J.B.Rainsberger 你好! EqualsVerifier 的创建者在这里。很抱歉让您感到困惑。回复:不覆盖等于:您可以使用“allFieldsShouldBeUsed()”启用您的预期行为。出于您陈述的原因,这将是下一个版本中的默认设置。回复:相同的实例:如果没有看到一些示例代码,我认为我无法帮助您。您能否在一个新问题或groups.google.com/forum/?fromgroups#!forum/equalsverifier 的 EV 邮件列表中详细说明?谢谢!
            • EqualsVerifier 真的很强大。通过不测试不等于对象的每个潜在组合,我获得了大量时间。错误消息非常准确且具有指导意义。如果您的编码约定与默认期望不同,验证器是非常可配置的。干得好@jqno
            【解决方案7】:

            【讨论】:

              【解决方案8】:

              如果我有一个类Thing,我会像大多数其他人一样编写一个类ThingTest,它包含该类的所有单元测试。每个ThingTest都有一个方法

               public static void checkInvariants(final Thing thing) {
                  ...
               }
              

              如果Thing 类覆盖hashCode 并且equals 它有一个方法

               public static void checkInvariants(final Thing thing1, Thing thing2) {
                  ObjectTest.checkInvariants(thing1, thing2);
                  ... invariants that are specific to Thing
               }
              

              该方法负责检查 所有 不变量,这些不变量旨在保存在任何一对 Thing 对象之间。它委托给的ObjectTest 方法负责检查任何一对对象之间必须保持的所有不变量。由于equalshashCode 是所有对象的方法,该方法检查hashCodeequals 是否一致。

              然后我有一些测试方法可以创建成对的Thing 对象,并将它们传递给成对的checkInvariants 方法。我使用等价分区来决定哪些对值得测试。我通常将每一对创建为仅在一个属性上有所不同,外加一个测试两个等效对象的测试。

              我有时也有一个 3 参数 checkInvariants 方法,虽然我发现它在查找缺陷方面不太有用,所以我不经常这样做

              【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-07-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-07-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多