【问题标题】:Assert equals between 2 Lists in Junit在 Junit 中断言等于 2 个列表之间
【发布时间】:2011-03-15 07:01:36
【问题描述】:

如何在 JUnit 测试用例中的列表之间进行相等断言?列表内容之间应该相等。

例如:

List<String> numbers = Arrays.asList("one", "two", "three");
List<String> numbers2 = Arrays.asList("one", "two", "three");
List<String> numbers3 = Arrays.asList("one", "two", "four"); 

// numbers should be equal to numbers2
//numbers should not be equal to numbers3

【问题讨论】:

  • 我现在喜欢使用assertArrayEquals。与List#toArray结合使用。
  • @Thibstars - 我赞成将其作为答案。
  • assertArrayEquals 要求您从列表中获取数组。可以直接使用assertIterableEquals对列表进行操作@
  • assertIterableEquals 可用于 jUnit5 @ThetaSinner

标签: java unit-testing collections junit


【解决方案1】:

对于junit4!这个问题值得为junit5 写一个新的答案。

我意识到这个答案是在提出问题几年后写的,可能当时还没有这个功能。但是现在,这样做很容易:

@Test
public void test_array_pass()
{
  List<String> actual = Arrays.asList("fee", "fi", "foe");
  List<String> expected = Arrays.asList("fee", "fi", "foe");

  assertThat(actual, is(expected));
  assertThat(actual, is(not(expected)));
}

如果您使用 hamcrest 安装了最新版本的 Junit,只需添加以下导入:

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

http://junit.org/junit4/javadoc/latest/org/junit/Assert.html#assertThat(T, org.hamcrest.Matcher)

http://junit.org/junit4/javadoc/latest/org/hamcrest/CoreMatchers.html

http://junit.org/junit4/javadoc/latest/org/hamcrest/core/Is.html

【讨论】:

  • System.out.println(actual == expected); 将返回 false,但 System.out.println(actual.equals(expected)); 将返回 true。
  • @Catfish 是的,这很令人困惑,不是吗。我想我是在证明匹配器使用的是.equals(..) 而不是==
  • 这比 assertEquals 好在哪里?
  • @Raedwald 断言失败时的输出。我会稍后再回来编辑差异。 hamcrest 匹配器可以生成详细的失败消息。 junit 可以简单地以类似的优点重载 assertEquals 。但主要是 junit 提供核心单元测试功能,而 hamcrest 提供了一个不错的对象差异描述器库。
  • @djeikyb, Assert.assertEquals 工作正常,并在比较失败时输出预期和实际列表。
【解决方案2】:

不要转换为字符串并进行比较。这对性能不利。 在junit中,在Corematchers中,有一个匹配器=> hasItems

List<Integer> yourList = Arrays.asList(1,2,3,4)    
assertThat(yourList, CoreMatchers.hasItems(1,2,3,4,5));

据我所知,这是检查列表中元素的更好方法。

【讨论】:

  • 应该是选择的答案,并附注:您还需要验证列表中除了您想要的之外没有其他项目。也许使用:Assert.assertEquals(4,yourList.size());
  • 或单行:assertThat(yourList.toArray(), arrayContainingInAnyOrder(1,2,3,4,5));
  • “这不利于性能。” → 我不确定在编写单元测试时应该将性能考虑到什么程度...但可以肯定的是,通过 toString() 版本比较字符串并不是最好的方法。
【解决方案3】:

来自 JUnit4/JUnit 5 的assertEquals(Object, Object) 或来自 Hamcrest 的assertThat(actual, is(expected)); 在其他答案中提出的仅当equals()toString() 被比较对象的类(和深度)覆盖时才有效。

这很重要,因为断言中的相等测试依赖于equals(),而测试失败消息依赖于比较对象的toString()
对于诸如StringInteger 之类的内置类……没问题,因为它们同时覆盖了equals()toString()。所以用assertEquals(Object,Object)断言List&lt;String&gt;List&lt;Integer&gt;是完全有效的。
关于这个问题:你必须在一个类中覆盖equals(),因为它在对象相等方面是有意义的,不仅是为了让 JUnit 测试中的断言更容易。
为了使断言更容易,您还有其他方法。
作为一个好习惯,我喜欢断言/匹配器库。

这是一个AssertJ 解决方案。

org.assertj.core.api.ListAssert.containsExactly() 是您所需要的:它验证实际组是否完全包含给定值,而没有其他内容,按照 javadoc 中所述的顺序。

假设有一个 Foo 类,您可以在其中添加元素以及获取元素的位置。
断言两个列表具有相同内容的Foo 的单元测试可能如下所示:

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

@Test
void add() throws Exception { 
   Foo foo = new Foo();
   foo.add("One", "Two", "Three");
   Assertions.assertThat(foo.getElements())
             .containsExactly("One", "Two", "Three");
}

AssertJ 的一个优点是不需要按预期声明 List:它使断言更直接,代码更易读:

Assertions.assertThat(foo.getElements())
         .containsExactly("One", "Two", "Three");

但是断言/匹配器库是必须的,因为它们真的会更进一步。
现在假设 Foo 不存储 Strings 而是 Bars 实例。
这是一个非常普遍的需求。 使用 AssertJ,断言仍然很容易编写。更好的是,即使元素的类没有覆盖equals()/hashCode(),您也可以断言列表内容是相等的,而 JUnit 方式要求这样做:

import org.assertj.core.api.Assertions;
import static org.assertj.core.groups.Tuple.tuple;
import org.junit.jupiter.api.Test;

@Test
void add() throws Exception {
    Foo foo = new Foo();
    foo.add(new Bar(1, "One"), new Bar(2, "Two"), new Bar(3, "Three"));
    Assertions.assertThat(foo.getElements())
              .extracting(Bar::getId, Bar::getName)
              .containsExactly(tuple(1, "One"),
                               tuple(2, "Two"),
                               tuple(3, "Three"));
}

【讨论】:

    【解决方案4】:

    这是一个遗留答案,适用于 JUnit 4.3 及更低版本。现代版本的 JUnit 在 assertThat 方法中包含一个内置的可读失败消息。如果可能的话,更喜欢这个问题的其他答案。

    List<E> a = resultFromTest();
    List<E> expected = Arrays.asList(new E(), new E(), ...);
    assertTrue("Expected 'a' and 'expected' to be equal."+
                "\n  'a'        = "+a+
                "\n  'expected' = "+expected, 
                expected.equals(a));
    

    作为记录,正如@Paul 在他对此答案的评论中提到的,两个Lists 是相等的:

    当且仅当指定对象也是一个列表时,两个列表的大小相同,并且两个列表中所有对应的元素对都相等。 (如果(e1==null ? e2==null : e1.equals(e2)),则两个元素e1e2 相等。)换句话说,如果两个列表以相同的顺序包含相同的元素,则它们被定义为相等。此定义确保 equals 方法在 List 接口的不同实现中正常工作。

    请参阅JavaDocs of the List interface

    【讨论】:

    • 所以你的意思是 expected.equals(a) 将负责断言列表持有的对象?
    • From List javadoc:比较指定对象与此列表是否相等。当且仅当指定的对象也是一个列表时返回 true,两个列表具有相同的大小,并且两个列表中所有对应的元素对都相等。 (如果 (e1==null ? e2==null : e1.equals(e2)) 两个元素 e1 和 e2 相等。)换句话说,如果两个列表以相同的顺序包含相同的元素,则它们被定义为相等.此定义确保 equals 方法在 List 接口的不同实现中正常工作。
    • 这很遗憾提供的错误信息并不实用。我发现编写一个执行循环的实用程序类更好,这样您就可以看到哪些元素不同。
    • @mlk,也许,但我很犹豫是否要为这样的事情编写自定义实用程序方法。我刚才编辑的错误信息呢?
    • @mlk 我同意编写一个循环来测试每个元素可能会更好,这样您就可以确切地知道有什么不同。这取决于列表中的对象类型。如果它们是字符串,那么只需说 "a="+a 应该没问题,但如果它们是复杂对象(其他列表,或者没有好的 toString 实现的东西),单独测试它们可能更容易
    【解决方案5】:

    对于 JUnit 5

    你可以使用assertIterableEquals

    List<String> numbers = Arrays.asList("one", "two", "three");
    List<String> numbers2 = Arrays.asList("one", "two", "three");
    
    Assertions.assertIterableEquals(numbers, numbers2);
    

    assertArrayEquals 并将列表转换为数组:

    List<String> numbers = Arrays.asList("one", "two", "three");
    List<String> numbers2 = Arrays.asList("one", "two", "three");
    Assertions.assertArrayEquals(numbers.toArray(), numbers2.toArray());
    

    【讨论】:

    • 升级到 JUnit5 要简单得多。 Migration Instructions 发件人:testImplementation("junit:junit:4.13") 收件人:testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0")
    【解决方案6】:

    如果你不关心元素的顺序,我推荐 junit-addons 中的ListAssert.assertEquals

    链接:http://junit-addons.sourceforge.net/

    对于懒惰的 Maven 用户:

        <dependency>
            <groupId>junit-addons</groupId>
            <artifactId>junit-addons</artifactId>
            <version>1.4</version>
            <scope>test</scope>
        </dependency>
    

    【讨论】:

    • 注意:如果你不关心元素的顺序,你应该使用 Set 或 Collection,而不是 List。
    • 我同意。这个图书馆很恶心。为什么 ListAssert.assertEquals() 会默认为无序?
    【解决方案7】:

    您可以在 junit 中使用 assertEquals

    import org.junit.Assert;   
    import org.junit.Test;
    
        @Test
        public void test_array_pass()
        {
            List<String> actual = Arrays.asList("fee", "fi", "foe");
            List<String> expected = Arrays.asList("fee", "fi", "foe");
            Assert.assertEquals(actual,expected);
        }
    

    如果元素的顺序不同,则会返回错误。

    如果您要断言模型对象列表,那么您应该 覆盖特定模型中的equals方法。

        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }
            if (obj != null && obj instanceof ModelName) {
                ModelName other = (ModelName) obj;
                return this.getItem().equals(other.getItem()) ;
            }
            return false;
        }
    

    【讨论】:

    • Assert.assertEquals 中参数的顺序是预期结果然后是实际结果,所以在你的 sn-p 中应该是 Assert.assertEquals(expected, actual);
    【解决方案8】:

    如果你不想建立一个数组列表,你也可以试试这个

    @Test
    public void test_array_pass()
    {
      List<String> list = Arrays.asList("fee", "fi", "foe");
      Strint listToString = list.toString();
      Assert.assertTrue(listToString.contains("[fee, fi, foe]"));   // passes  
    }
    

    【讨论】:

      【解决方案9】:
      List<Integer> figureTypes = new ArrayList<Integer>(
                                 Arrays.asList(
                                       1,
                                       2
                                  ));
      
      List<Integer> figureTypes2 = new ArrayList<Integer>(
                                 Arrays.asList(
                                       1,
                                       2));
      
      assertTrue(figureTypes .equals(figureTypes2 ));
      

      【讨论】:

        【解决方案10】:

        我知道已经有很多选项可以解决这个问题,但我宁愿执行以下操作来以任意顺序断言两个列表

        assertTrue(result.containsAll(expected) && expected.containsAll(result))
        

        【讨论】:

        • 如果订单不匹配会不会失败??
        【解决方案11】:

        您提到您对列表内容的相等性感兴趣(并且没有提及顺序)。所以来自 AssertJ 的containsExactlyInAnyOrder 非常合适。例如,它附带spring-boot-starter-test

        来自AssertJ docs ListAssert#containsExactlyInAnyOrder

        验证实际组是否完全包含给定的值,并且不包含任何其他内容,顺序不限。 示例:

         // an Iterable is used in the example but it would also work with an array
         Iterable<Ring> elvesRings = newArrayList(vilya, nenya, narya, vilya);
        
         // assertion will pass
         assertThat(elvesRings).containsExactlyInAnyOrder(vilya, vilya, nenya, narya);
        
         // assertion will fail as vilya is contained twice in elvesRings.
         assertThat(elvesRings).containsExactlyInAnyOrder(nenya, vilya, narya);
        

        【讨论】:

          【解决方案12】:

          assertEquals(expected, result); 为我工作。 由于这个函数有两个对象,你可以向它传递任何东西。

          public static void assertEquals(Object expected, Object actual) {
              AssertEquals.assertEquals(expected, actual);
          }
          

          【讨论】:

            【解决方案13】:

            我不认为以上所有答案都给出了比较两个对象列表的确切解决方案。 上述大多数方法仅有助于遵循比较限制 - 尺寸比较 - 参考比较

            但是,如果我们有相同大小的对象列表和对象级别的不同数据,那么这种比较方法将无济于事。

            我认为以下方法可以完美地用于覆盖用户定义对象的 equals 和 hashcode 方法。

            我使用Xstream lib 来覆盖等于和哈希码,但我们也可以通过胜出的逻辑/比较来覆盖等于和哈希码。

            这里是你参考的例子

                import com.thoughtworks.xstream.XStream;
            
                import java.text.ParseException;
                import java.util.ArrayList;
                import java.util.List;
            
                class TestClass {
                  private String name;
                  private String id;
            
                  public void setName(String value) {
                    this.name = value;
                  }
            
                  public String getName() {
                    return this.name;
                  }
            
                  public String getId() {
                    return id;
                  }
            
                  public void setId(String id) {
                    this.id = id;
                  }
            
                  /**
                   * @see java.lang.Object#equals(java.lang.Object)
                   */
                  @Override
                  public boolean equals(Object o) {
                    XStream xstream = new XStream();
                    String oxml = xstream.toXML(o);
                    String myxml = xstream.toXML(this);
            
                    return myxml.equals(oxml);
                  }
            
                  /**
                   * @see java.lang.Object#hashCode()
                   */
                  @Override
                  public int hashCode() {
                    XStream xstream = new XStream();
                    String myxml = xstream.toXML(this);
                    return myxml.hashCode();
                  }
                }
            
                public class XstreamCompareTest {
                  public static void main(String[] args) throws ParseException {
                  checkObjectEquals();
            }
            
                  private static void checkObjectEquals() {
                    List<TestClass> testList1 = new ArrayList<TestClass>();
                    TestClass tObj1 = new TestClass();
                    tObj1.setId("test3");
                    tObj1.setName("testname3");
                    testList1.add(tObj1);
            
                    TestClass tObj2 = new TestClass();
                    tObj2.setId("test2");
                    tObj2.setName("testname2");
                    testList1.add(tObj2);
            
                    testList1.sort((TestClass t1, TestClass t2) -> t1.getId().compareTo(t2.getId()));
            
                    List<TestClass> testList2 = new ArrayList<TestClass>();
                    TestClass tObj3 = new TestClass();
                    tObj3.setId("test3");
                    tObj3.setName("testname3");
                    testList2.add(tObj3);
            
                    TestClass tObj4 = new TestClass();
                    tObj4.setId("test2");
                    tObj4.setName("testname2");
                    testList2.add(tObj4);
            
                    testList2.sort((TestClass t1, TestClass t2) -> t1.getId().compareTo(t2.getId()));
            
                    if (isNotMatch(testList1, testList2)) {
                      System.out.println("The list are not matched");
                    } else {
                      System.out.println("The list are matched");
                    }
            
                  }
            
                  private static boolean isNotMatch(List<TestClass> clist1, List<TestClass> clist2) {
                    return clist1.size() != clist2.size() || !clist1.equals(clist2);
                  }
                }
            

            最重要的是,如果您不想在 Objects 的相等检查中包含任何字段,您可以通过 Annotation (@XStreamOmitField) 忽略字段。有很多这样的注解需要配置,所以请深入了解这个库的注解。

            我相信这个答案将节省您确定比较两个对象列表的正确方法的时间:)。如果您对此有任何问题,请发表评论。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2010-09-22
              • 1970-01-01
              • 2011-11-12
              • 1970-01-01
              • 1970-01-01
              • 2018-12-23
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多