【问题标题】:Assert that two java beans are equivalent断言两个 java bean 是等价的
【发布时间】:2010-12-16 23:55:17
【问题描述】:

This question 很接近,但仍然不是我想要的。我想以一种通用的方式断言两个 bean 对象是等价的。如果不是,我想要一个详细的错误消息来解释差异而不是布尔“等于”或“不等于”。

【问题讨论】:

  • 你可能会发现这篇博文很有启发性blogs.atlassian.com/developer/2009/06/…
  • @Chii:你有什么理由把这个作为评论发布吗?这是一个很好的答案!
  • 这是一个元问题:您像其他人一样混淆了“相同”、“相等”和“等价”。
  • @bendin - 你是对的:相等和等价不一样;)

标签: java unit-testing assert javabeans equality


【解决方案1】:

xtendbeans library 在这种情况下可能很有趣:

AssertBeans.assertEqualBeans(expectedBean, actualBean);

这会产生一个 JUnit ComparisonFailure à la:

expected: 
    new Person => [
        firstName = 'Homer'
        lastName = 'Simpson'
        address = new Address => [
          street = '742 Evergreen Terrace'
          city = 'SpringField'
       ]
  ]
but was:
    new Person => [
        firstName = 'Marge'
        lastName = 'Simpson'
        address = new Address => [
          street = '742 Evergreen Terrace Road'
          city = 'SpringField'
       ]
  ]

您也可以将其仅用于获取文本表示以用于其他目的:

String beanAsLiteralText = new XtendBeanGenerator().getExpression(yourBean)

使用这个库,您可以使用上述语法上有效的对象初始化代码片段将其复制/粘贴到expectedBean 的(Xtend)源类中,但您不必这样做,它可以很好地使用也没有 Xtend。

【讨论】:

    【解决方案2】:

    您可以像这样设置所有字段:

    import static org.hamcrest.MatcherAssert.assertThat;
    import static org.hamcrest.Matchers.allOf;
    import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
    import static org.hamcrest.Matchers.is;
    
    @Test
    public void test_returnBean(){
    
        arrange();
    
        MyBean myBean = act();
    
        assertThat(myBean, allOf(hasProperty("id",          is(7L)), 
                                 hasProperty("name",        is("testName1")),
                                 hasProperty("description", is("testDesc1"))));
    }
    

    【讨论】:

      【解决方案3】:

      对于单元测试,这可以通过 JUnit 和 Mockito 使用 ReflectionEquals 来完成。当以以下方式实现时,它会在任何字段不相等时转储对象的 JSON 表示形式,这样很容易找到有问题的差异。

      import static org.junit.Assert.assertThat;
      import org.mockito.internal.matchers.apachecommons.ReflectionEquals;
      
      assertThat("Validating field equivalence of objects", expectedObjectValues, new ReflectionEquals(actualObjectValues));
      

      【讨论】:

        【解决方案4】:
        import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
        import static org.junit.Assert.assertThat;
        
        @Test
        public void beansAreTheSame(){
            MyDomianClass bean1 = new MyDomainClass();
            MyDomianClass bean2 = new MyDomainClass();
            //TODO - some more test logic
        
            assertThat(bean1, samePropertyValuesAs(bean2));
        }
        

        【讨论】:

        • 这需要更多可见性!
        • 不会比较深层对象
        • @AndreyRodionov:同意 - 这很糟糕。不过,总比没有好。
        【解决方案5】:

        我建议你使用 unitils 库:

        http://www.unitils.org/tutorial-reflectionassert.html

        公共类用户{ 私人长ID; 私有字符串优先; 私有字符串最后; 公共用户(长ID,字符串第一,字符串最后){ 这个.id = id; this.first = 第一个; this.last = 最后一个; } } 用户 user1 = new User(1, "John", "Doe"); 用户 user2 = new User(1, "John", "Doe"); assertReflectionEquals(user1, user2);

        另见:

        【讨论】:

        • 好答案!,我不知道,为每个属性使用 assertEquals 编写单元非常痛苦
        • 我不得不说这个库非常酷。如果测试失败,它会准确报告导致失败的字段以及它们的值。干得好。
        【解决方案6】:

        我认为,最通用的方法是反映 bean 成员并逐一测试它们是否相等。通用语言的EqualsBuilder 是一个好的开始,它应该不是什么大问题,可以(在源代码级别)使其适应您的要求(报告差异而不是返回等于结果)。

        【讨论】:

        • -1 没有给出详细的错误信息有什么不同。
        • 我也使用过这个,但略有不同。为了在使用 EqualsBuilder 后也能得到有意义的错误信息。 reflectEquals 我在两个 ToStringBuilder.reflectionToString 上做了一个断言(如果 to 字符串实际上是相同的,最后用一个 fail(...) 成功)。然后 Eclipse 将突出显示两个对象之间的差异。
        • @Aaron - 我知道,这就是我建议调整 EqualsBuilder 代码的原因。
        【解决方案7】:

        (以我对上述Andreas_D 的评论为基础)

        /** Asserts two objects are equals using a reflective equals.
         * 
         * @param message The message to display.
         * @param expected The expected result.
         * @param actual The actual result
         */
        public static void assertReflectiveEquals(final String message, 
                final Object expected, final Object actual) {
            if (!EqualsBuilder.reflectionEquals(expected, actual)) {
                assertEquals(message, 
                        reflectionToString(expected, ToStringStyle.SHORT_PREFIX_STYLE), 
                        reflectionToString(actual, ToStringStyle.SHORT_PREFIX_STYLE));
                fail(message + "expected: <" + expected + ">  actual: <" + actual + ">");
            }
        }
        

        这是我使用的,我相信它满足所有基本要求。通过在反射 ToString 上执行断言,Eclipse 将突出显示差异。

        虽然Hamcrest 可以提供更好的信息,但这确实需要更少的代码。

        【讨论】:

          【解决方案8】:

          我要问的第一个问题是,你想在 Bean 上做“深度”等于吗?它是否有需要测试的子 bean?您可以重写 equals 方法,但这只会返回一个布尔值,因此您可以创建一个“比较器”,这可能会引发异常并显示不相等的消息。

          在以下示例中,我列出了几种实现 equals 方法的方法。

          如果你想检查它们是否是同一个对象实例,那么来自 Object 的普通 equals 方法会告诉你。

              objectA.equals(objectB);
          

          如果你想编写一个客户的equals方法来检查一个对象的所有成员变量是否使它们相等,那么你可以像这样覆盖equals方法......

            /**
               * Method to check the following...
               * <br>
               * <ul>
               *    <li>getTitle</li>
               *    <li>getInitials</li>
               *    <li>getForename</li>
               *    <li>getSurname</li>
               *    <li>getSurnamePrefix</li>
               * </ul>
               *
               * @see java.lang.Object#equals(java.lang.Object)
               */
              @Override
              public boolean equals(Object obj)
              {
                if (   (!compare(((ICustomer) obj).getTitle(), this.getTitle()))
                    || (!compare(((ICustomer) obj).getInitials(), this.getInitials()))
                    || (!compare(((ICustomer) obj).getForename(), this.getForename()))
                    || (!compare(((ICustomer) obj).getSurname(), this.getSurname()))
                    || (!compare(((ICustomer) obj).getSurnamePrefix(), this.getSurnamePrefix()))
                    || (!compare(((ICustomer) obj).getSalutation(), this.getSalutation()))  ){
                    return false;
                }
                return true;
              }
          

          最后一个选项是使用 java 反射检查 equals 方法中的所有成员变量。如果您真的想通过其 bean get/set 方法检查每个成员变量,这非常棒。当两个对象的测试相同时,它不会(我不认为)允许您检查私有成员变量。 (如果你的对象模型有循环依赖,不要这样做,它永远不会返回)

          注意:这不是我的代码,它来自...

          Java Reflection equals 公共静态布尔等于(对象 bean1,对象 bean2) { // 处理琐碎的情况 如果(bean1 == bean2) 返回真;

          if (bean1 == null)
          return false;
          
          if (bean2 == null)
          return false;
          
          // Get the class of one of the parameters
          Class clazz = bean1.getClass();
          
          // Make sure bean1 and bean2 are the same class
          if (!clazz.equals(bean2.getClass()))
          {
          return false;
          }
          
          // Iterate through each field looking for differences
          Field[] fields = clazz.getDeclaredFields();
          for (int i = 0; i < fields.length; i++)
          {
          // setAccessible is great (encapsulation
          // purists will disagree), setting to true
          // allows reflection to have access to
          // private members.
          fields[i].setAccessible(true);
          try
          {
          Object value1 = fields[i].get(bean1);
          Object value2 = fields[i].get(bean2);
          
          if ((value1 == null && value2 != null) ||
          (value1 != null && value2 == null))
          {
          return false;
          }
          
          if (value1 != null &&
          value2 != null &&
          !value1.equals(value2))
          {
          return false;
          }
          }
          catch (IllegalArgumentException e)
          {
          e.printStackTrace();
          }
          catch (IllegalAccessException e)
          {
          e.printStackTrace();
          }
          }
          
          return true;
          

          这并不能告诉您差异的原因,但是当您发现不相等的部分时,可以通过向 Log4J 发送消息来完成。

          【讨论】:

          • -1 这只是说“有区别”,但不知道可能有什么区别。
          • 在单元测试时甚至不需要有日志。失败的测试应该告诉你所有你需要知道的。
          【解决方案9】:

          我在这里假设两个 bean 属于同一类型,在这种情况下,只有成员变量值在 bean 实例之间会有所不同。

          定义一个名为 BeanAssertEquals 的 util 类(带有私有 ctor 的 public static final)。使用Java反射来获取每个bean中每个成员变量的值。然后在不同bean中相同成员变量的值之间执行equals()。如果相等失败,请提及字段名称。

          注意:成员变量通常是私有的,因此您需要使用反射来临时更改私有成员的可访问性。

          此外,根据您希望断言工作的细粒度,您应该考虑以下几点:

          1. 成员变量的相等性不是在 bean 类中而是在所有超类中。

          2. 数组中元素的相等性,以防成员变量是数组类型。

          3. 对于跨 bean 的给定成员的两个值,您可以考虑使用 BeanAssertEquals.assertEquals(value1, value2) 而不是 value1.equals(value2)。

          【讨论】:

            【解决方案10】:

            您可以使用Commons LangToStringBuilder 将它们都转换为可读字符串,然后在两个字符串上使用assertEquals()

            如果你喜欢XML,可以使用java.lang.XMLEncoder将你的bean转成XML,然后比较两个XML文档。

            就个人而言,我更喜欢ToStringBuilder,因为它可以让您更好地控制格式并允许您执行诸如对集合中的元素进行排序以避免误报等操作。

            我建议将 bean 的每个字段放在不同的行中,以便更轻松地比较它们 (see my blog for details)。

            【讨论】:

            • 太好了,我不知道这个。实际上,我非常需要这样的东西。谢谢朋友。
            【解决方案11】:

            您并没有真正主张平等,而是在做“差异”。显然,“相同”的含义取决于每种类型的特定逻辑,并且差异的表示也可能有所不同。此要求与传统的 equals() 之间的一个主要区别是,通常 equals() 会在看到第一个区别后立即停止,您将需要继续并比较每个字段。

            我会考虑重用一些 equals() 模式,但我怀疑您需要编写自己的代码。

            【讨论】:

              【解决方案12】:

              既然你不喜欢你引用的问题中的答案,为什么不在每个 bean 中设置一个 toXml 方法,将它们转换为 xml 文件,然后使用 xmlUnit 进行比较。

              您可以在此处获取有关比较 xml 文件的更多信息:

              Best way to compare 2 XML documents in Java

              【讨论】:

              • 我唯一担心的是可能需要排除某些属性,通过使用 toXml() 它强制他知道正在比较的内容,否则您的评论会更正确。
              猜你喜欢
              • 2016-03-04
              • 1970-01-01
              • 2022-05-01
              • 2022-04-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-29
              • 2017-11-07
              相关资源
              最近更新 更多