AssertEquals 2列表忽略顺序


82

我认为那应该是一个非常简单的问题。但是不知何故我无法在Google中找到答案。

假设我有2个字符串列表。第一个包含“字符串A”和“字符串B”,第二个包含“字符串B”和“字符串A”(注意顺序不同)。我想用JUnit测试它们,以检查它们是否包含完全相同的字符串。

是否有任何断言可以检查忽略顺序的字符串是否相等?对于给定的示例org.junit.Assert.assertEquals抛出AssertionError

java.lang.AssertionError: expected:<[String A, String B]> but was:<[String B, String A]>

解决方法是先对列表进行排序,然后再将它们传递给断言。但是我希望我的代码尽可能简单和干净。

我使用Hamcrest 1.3JUnit 4.11Mockito 1.9.5


3
list1.removeAll(list2)应该list1留空。我想您可以在此基础上获得想要的东西。
SudoRahul

6
containsAllremoveAllO(n²)列表排序时和测试是否相等O(nlogn)Collections.sort(list1); Collections.sort(list2); assertTrue(list1.equals(list2));也很干净。
Alexis C.


@SudoRahul-如果您不想通过删除全部内容来修改列表怎么办?
艾伦·莫拉德

@BoratSagdiyev-由于这不是OP的限制,所以我建议这样做。但是,如果这是一个约束,那么这个问题的公认答案就可以解决当前的问题。
SudoRahul

Answers:


92

正如您提到使用Hamcrest一样,我会选择Matchers之一

import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
import static org.junit.Assert.assertThat;

public class CompareListTest {

    @Test
    public void compareList() {
        List<String> expected = Arrays.asList("String A", "String B");
        List<String> actual = Arrays.asList("String B", "String A");

        assertThat("List equality without order", 
            actual, containsInAnyOrder(expected.toArray()));
    }

}

5
另请参阅我的答案stackoverflow.com/a/38262680/297710,其中显示了如何在每个包含containsInAnyOrder的断言中改进Hamcrest匹配器并避免“ .toArray()”
yvolk

57

您可以将List.containsAll与JUnit的assertTrue一起使用,以检查第一个列表是否包含第二个列表中的每个元素,反之亦然。

assertTrue(first.size() == second.size() && 
    first.containsAll(second) && second.containsAll(first));

2
@kukis这取决于您是否要检查重复项?
robertoia 2014年

4
当然是。2个给定的列表必须完全相同,只是忽略顺序。
kukis 2014年

2
@kukis然后检查ZouZou对您的问题的评论。
robertoia 2014年

1
..可能包括assertEquals(first.size(), second.size())..那么它应该会按预期工作
绝对

17
这不适用于列表中的重复项。这是一个示例:List<String> list1 = Arrays.asList("a", "a", "b"); List<String> list2 = Arrays.asList("a", "b", "b"); assertEquals(list1.size(), list2.size()); assertTrue(list1.containsAll(list2) && list2.containsAll(list1)); 在此示例中,两个断言都无法检测到列表实际上是不同的。@AlexWorden提到了Apache Commons Collections的CollectionUtils.isEqualCollection(),在此示例中,它正确地检测到集合不相等。
desilvai

11

这是一种避免二次复杂性的解决方案(多次遍历列表)。这使用Apache Commons CollectionUtils类来创建每个项目的映射,以将其映射到列表中的频率计数本身。然后,它仅比较两个Map。

Assert.assertEquals("Verify same metrics series",
    CollectionUtils.getCardinalityMap(expectedSeriesList),
    CollectionUtils.getCardinalityMap(actualSeriesList));

我还发现了CollectionUtils.isEqualCollection,声称它确实可以执行此处要求的操作...

https://commons.apache.org/proper/commons-collections/apidocs/index.html?org/apache/commons/collections4/CollectionUtils.html


4

使用AssertJ,containsExactlyInAnyOrder()还是containsExactlyInAnyOrderElementsOf()您需要的是:

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

import java.util.Arrays;
import java.util.List;

public class CompareListTest {

    @Test
    public void compareListWithTwoVariables() {
        List<String> expected = Arrays.asList("String A", "String B");
        List<String> actual = Arrays.asList("String B", "String A");
        Assertions.assertThat(actual)
                  .containsExactlyInAnyOrderElementsOf(expected);
    }

    @Test
    public void compareListWithInlineExpectedValues() {
        List<String> actual = Arrays.asList("String B", "String A");
        Assertions.assertThat(actual)
                  .containsExactlyInAnyOrder("String A", "String B");
    }    
}

3
    Collections.sort(excepted);
    Collections.sort(actual);
    assertEquals(excepted,actual);

2

我晚会晚了,但这是我仅使用Junit的解决方案。任何想法都欢迎。

List<String> actual = new ArrayList<>();
actual.add("A");
actual.add("A");
actual.add("B");

List<String> expected = new ArrayList<>();
actual.add("A");
actual.add("B");
actual.add("B");

//Step 1: assert for size
assertEquals(actual.size(), expected.size());

//Step 2: Iterate
for(String e: expected){
    assertTrue(actual.contains(e));
    actual.remove(e);
}

1

请注意,Roberto Izquierdo的解决方案通常具有二次复杂度。HashSets上的解决方案始终具有线性复杂度:

assertTrue(first.size() == second.size() &&
        new HashSet(first).equals(new HashSet(second)));

2
这种方法行不通。如果第一个是(“ String A”),第二个是(“ String A”,“ String A”),则它们不是相同的列表。
Alexis C.

4
您无法检查尺寸。如果first是("s1", "s2", "s3" ,"s1"),second是("s2", "s1", "s3" ,"s2");不相同的列表。
Alexis C.

@ZouZou接受的解决方案具有相同的问题。您提出了唯一真正正确的解决方案。如果您有答案,我会赞成。
leventov

@ZouZou它们不是相同的列表,但是它们包含完全相同的字符串。OP,澄清吗?另外,将其作为答案,我也将投票赞成:)没有想到这一点。
robertoia 2014年

2
对于所有情况(“ A”,“ A”,“ B”)将等于(“ A”,“ B”,“ B”)的情况,这仍然是不正确的
Tim B

1

为了快速解决问题,我将同时检查两种方法:

assertTrue(first.containsAll(second));
assertTrue(second.containsAll(first));

并尝试在相同元素的数量不同(例如1、1、2和1、2、2)的情况下,我没有得到误报。


1
您的代码仍然失败。参见此示例-@Test public void test1(){List <String> list1 = Arrays.asList(“ a”,“ a”,“ b”); List <String> list2 = Arrays.asList(“ a”,“ b”,“ b”); Assert.assertTrue(list1.containsAll(list2)); Assert.assertTrue(list2.containsAll(list1)); }
Erran Morad's

1

您可以使用junit-addons jar中附带的ListAssert

ListAssert.assertEquals(yourList, Arrays.asList(3, 4, 5));

0

看起来其他答案都引用了第三方工具,不正确或效率低下。

这是Java 8中的O(N)原始解决方案。

public static void assertContainsSame(Collection<?> expected, Collection<?> actual)
{
    assert expected.size() == actual.size();

    Map<Object, Long> counts = expected.stream()
        .collect(Collectors.groupingBy(
                item -> item,
                Collectors.counting()));

    for (Object item : actual)
        assert counts.merge(item, -1L, Long::sum) != -1L;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.