如何JUnit测试两个List <e>是否包含相同顺序的相同元素?</e>

时间:2012-09-19 13:10:08

标签: java junit guava

上下文

我正在为MyObject课程编写一个简单的 JUnit 测试。

可以从采用 String 的变量的静态工厂方法创建MyObject

MyObject.ofComponents("Uno", "Dos", "Tres");

MyObject存在的任何时候,客户都可以通过.getComponents() List<E> 的形式检查其创建的参数方法

myObject.ofComponents(); // -> List<String>: { "Uno", "Dos", "Tres" }

换句话说,MyObject都会记住并公开使其存在的参数列表。关于这份合同的更多细节:

  • getComponents的顺序与为对象创建选择的顺序相同
  • 允许重复后续 String 组件并按顺序保留
  • null上的行为未定义(其他代码保证没有null到达工厂)
  • 在对象实例化后无法更改组件列表

我正在编写一个简单的测试,从 String 列表中创建MyObject,并检查它是否可以通过.getComponents()返回相同的列表。 我立即执行此操作,但这应该是在实际代码路径中的某个距离

代码

这是我的尝试:


List<String> argumentComponents = Lists.newArrayList("One", "Two", "Three");
List<String> returnedComponents =
    MyObject.ofComponents(
        argumentComponents.toArray(new String[argumentComponents.size()]))
        .getComponents();
assertTrue(Iterables.elementsEqual(argumentComponents, returnedComponents));

问题

  • Google Guava Iterables.elementsEqual()最佳方式,只要我在构建路径中有库,就可以比较这两个列表吗? 这是我一直在苦恼的事情;我应该使用这个超过Iterable<E> ..检查大小的辅助方法,然后迭代运行.equals() ..或互联网搜索建议的任何其他方法?比较单元测试列表的规范方法是什么?

我很想获得的可选见解

  • 方法测试是否合理设计?我不是 JUnit
  • 的专家
  • .toArray()是将 List<E> 转换为E的varargs的最佳方法吗?

8 个答案:

答案 0 :(得分:69)

为什么不简单地使用List#equals

assertEquals(argumentComponents, imapPathComponents);

Contract of List#equals

  

如果两个列表包含相同顺序的相同元素,则它们被定义为相等。

答案 1 :(得分:50)

我更喜欢使用Hamcrest,因为它可以在出现故障时提供更好的输出

Assert.assertThat(listUnderTest, 
       IsIterableContainingInOrder.contains(expectedList.toArray()));

而不是报告

expected true, got false

它将报告

expected List containing "1, 2, 3, ..." got list containing "4, 6, 2, ..."

IsIterableContainingInOrder.contain

Hamcrest

根据Javadoc:

  

为Iterables创建一个匹配器,当对检查的Iterable进行单次传递时会产生一系列项目,每个项目在逻辑上等于指定项目中的相应项目。对于肯定匹配,检查的可迭代必须与指定项的数量

具有相同的长度

因此listUnderTest必须具有相同数量的元素,并且每个元素必须按顺序匹配预期值。

答案 2 :(得分:10)

List实现上的equals()方法应该进行元素比较,所以

assertEquals(argumentComponents, returnedComponents);

更容易。

答案 3 :(得分:9)

org.junit.Assert.assertEquals()org.junit.Assert.assertArrayEquals()完成这项工作。

要避免下一个问题:如果您想忽略订单,请设置所有元素然后进行比较:Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))

但是,如果您只是想忽略重复项,但保留您使用LinkedHashSet列出的订单包装。

又一个提示。技巧Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))正常工作,直到比较失败。在这种情况下,它会向您显示错误消息,其中包含您的集合的字符串表示,这可能会令人困惑,因为集合中的顺序几乎不可预测(至少对于复杂对象而言)。所以,我发现的技巧是使用有序集而不是HashSet包装集合。您可以将TreeSet与自定义比较器一起使用。

答案 4 :(得分:5)

为了获得出色的代码可读性,Fest Assertionsasserting lists

提供了很好的支持

所以在这种情况下,例如:

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

或者将预期的列表添加到数组中,但我更喜欢上述方法,因为它更清晰。

Assertions.assertThat(returnedComponents).containsExactly(argumentComponents.toArray());

答案 5 :(得分:3)

assertTrue()/ assertFalse():仅用于声明返回的布尔结果

  

assertTrue(Iterables.elementsEqual(argumentComponents,   returnComponents));

您要使用Assert.assertTrue()Assert.assertFalse(),因为被测方法返回一个boolean值。
当方法返回特定的事物(例如List)时,该事物应包含一些预期的元素,因此以assertTrue()进行声明:Assert.assertTrue(myActualList.containsAll(myExpectedList)  是反模式。
它使断言易于编写,但由于测试失败,因此也使调试变得困难,因为测试运行器只会对您说:

  

预期true,但实际为false

JUnit4中的

Assert.assertEquals(Object, Object)或JUnit 5中的Assertions.assertIterableEquals(Iterable, Iterable):仅当equals()toString()都被(比较对象

这很重要,因为断言中的相等性测试依赖于equals(),而测试失败消息依赖于比较对象的toString()
由于String会同时覆盖equals()toString(),因此用List<String>来断言assertEquals(Object,Object)是完全有效的。 关于此问题:您必须在类中重写equals(),因为在对象相等性方面它是有意义的,不仅要使使用JUnit进行测试时的断言更容易。
为了使声明更容易,您可以使用其他方法(可以在答案的接下来的部分中看到)。

番石榴是执行/构建单元测试断言的一种方法吗?

  

如果我的构建路径中有该库来比较这两个列表,那么Google Guava Iterables.elementsEqual()是最好的方法吗?

不,不是。 Guava不是编写单元测试断言的库。
您不需要它来编写大多数(我认为)的单元测试。

比较单元测试列表的规范方法是什么?

作为一个好的实践,我赞成断言/匹配器库。

我不能鼓励JUnit执行特定的断言,因为它提供的功能实在太少且功能有限:它仅执行带有深等号的断言。
有时您希望允许元素中的任何顺序,有时您希望允许期望中的任何元素与实际元素匹配,等等……

因此,使用单元测试断言/匹配器库(例如Hamcrest或AssertJ)是正确的方法。
实际答案提供了Hamcrest解决方案。这是一个AssertJ解决方案。

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

  

验证实际组是否完全包含给定的值,并且   没什么,按顺序。

您的测试可能如下所示:

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

@Test
void ofComponent_AssertJ() throws Exception {
   MyObject myObject = MyObject.ofComponents("One", "Two", "Three");
   Assertions.assertThat(myObject.getComponents())
             .containsExactly("One", "Two", "Three");
}

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

Assertions.assertThat(myObject.getComponents())
         .containsExactly("One", "Two", "Three");

如果测试失败:

// Fail : Three was not expected 
Assertions.assertThat(myObject.getComponents())
          .containsExactly("One", "Two");

您会收到一条非常清晰的消息,例如:

  

java.lang.AssertionError:

     

期望:

     

<[“一个”,“两个”,“三个”]>

     

精确包含(并以相同顺序):

     

<[“一个”,“两个”]>

     

但是没有预期的某些元素:

     

<[“三个”]>

断言/匹配器库是必须的,因为它们确实会更进一步

假设MyObject不存储String,而是存储Foo的实例,例如:

public class MyFooObject {

    private List<Foo> values;
    @SafeVarargs
    public static MyFooObject ofComponents(Foo... values) {
        // ...
    }

    public List<Foo> getComponents(){
        return new ArrayList<>(values);
    }
}

这是非常普遍的需求。 使用AssertJ,断言仍然很容易编写。更好的是,即使JUnit方式要求元素的类没有覆盖equals()/hashCode(),您也可以断言列表内容是相等的。

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

@Test
void ofComponent() throws Exception {
    MyFooObject myObject = MyFooObject.ofComponents(new Foo(1, "One"), new Foo(2, "Two"), new Foo(3, "Three"));

    Assertions.assertThat(myObject.getComponents())
              .extracting(Foo::getId, Foo::getName)
              .containsExactly(tuple(1, "One"),
                               tuple(2, "Two"),
                               tuple(3, "Three"));
}

答案 6 :(得分:1)

  • 关于Iterables.elementsEqual是否是最佳选择的答案:

Iterables.elementsEqual足以比较2 List s。

Iterables.elementsEqual用于更一般的场景,它接受更多常规类型:Iterable。也就是说,您甚至可以将ListSet进行比较。 (按迭代顺序,这很重要)

当然ArrayListLinkedList定义相当不错,你可以直接调用equals。当您使用定义不明确的列表时,Iterables.elementsEqual是最佳选择。有一点需要注意:Iterables.elementsEqual不接受null

  • 要将List转换为数组:Iterables.toArray更容易。

  • 对于单元测试,我建议在测试用例中添加空列表

答案 7 :(得分:0)

有些解决方案是有道理的,我同意它们。但对我来说,我会使用 assertEquals 但我会对两个列表进行排序。

assertEquals(sortedExpectedList, sortedActualList);

它很简单,输出仍然为您提供实际和预期之间的差异。