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

Rob*_*ino 72 java junit guava

上下文

我正在为该类编写一个简单的JUnit测试MyObject.

MyObject可以从静态工厂方法,该方法的可变参数来创建字符串.

MyObject.ofComponents("Uno", "Dos", "Tres");
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

我正在编写一个简单的测试,它MyObjectString列表中创建一个并检查它是否可以返回相同的列表.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));
Run Code Online (Sandbox Code Playgroud)

  • 谷歌番石榴 Iterables.elementsEqual()最好的办法,只要我有库在我的构建路径,那些两个列表比较?这是我一直在苦恼的事情; 我应该使用这个辅助方法,它超过了Iterable <E> ..检查大小,然后迭代运行.equals()..或互联网搜索建议的任何其他方法?比较单元测试列表的规范方法是什么?

我希望得到的可选见解

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

ass*_*ias 71

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

assertEquals(argumentComponents, imapPathComponents);
Run Code Online (Sandbox Code Playgroud)

合同List#equals:

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

  • 这不是使用Hamcrest的绝佳机会吗? (2认同)
  • 就像不使用外部库一样 (2认同)

Joh*_*n B 54

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

Assert.assertThat(listUnderTest, 
       IsIterableContainingInOrder.contains(expectedList.toArray()));
Run Code Online (Sandbox Code Playgroud)

而不是报告

expected true, got false

它会报告

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

IsIterableContainingInOrder.contain

Hamcrest

根据Javadoc:

为Iterables创建匹配器,当对检查的Iterable进行单次传递时会生成一系列项目,每个项目在逻辑上等于指定项目中的相应项目.对于肯定匹配,检查的iterable必须与指定项的数量具有相同的长度

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


小智 10

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

assertEquals(argumentComponents, returnedComponents);
Run Code Online (Sandbox Code Playgroud)

更容易.


Ale*_*exR 10

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自定义比较器.

  • 使用HashSet不会保留顺序和*重复*. (7认同)

dav*_*xxx 6

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

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

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

预期true但实际是false

Assert.assertEquals(Object, Object)在 JUnit4 或Assertions.assertIterableEquals(Iterable, Iterable)JUnit 5 中:仅用作两者equals(),并toString()为比较对象的类(和深度)覆盖

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

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

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

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

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

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

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

因此,使用单元测试断言/匹配器库(例如 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");
}
Run Code Online (Sandbox Code Playgroud)

AssertJ 的一个优点是List不需要按预期声明 a :它使断言更直接并且代码更具可读性:

Assertions.assertThat(myObject.getComponents())
         .containsExactly("One", "Two", "Three");
Run Code Online (Sandbox Code Playgroud)

如果测试失败:

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

您会收到非常明确的消息,例如:

java.lang.断言错误:

期待:

<[“一”、“二”、“三”]>

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

<[“一”,“二”]>

但有些内容是出乎意料的:

<[“三”]>

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

假设MyObject不存储Strings 而是Foos 实例,例如:

public class MyFooObject {

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

    public List<Foo> getComponents(){
        return new ArrayList<>(values);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个非常普遍的需求。使用 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 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"));
}
Run Code Online (Sandbox Code Playgroud)


cru*_*dog 5

为了获得出色的代码可读性,Fest Assertions断言列表提供了很好的支持

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

Assertions.assertThat(returnedComponents).containsExactly("One", "Two", "Three");
Run Code Online (Sandbox Code Playgroud)

或者将预期列表设置为数组,但我更喜欢上述方法,因为它更清晰.

Assertions.assertThat(returnedComponents).containsExactly(argumentComponents.toArray());
Run Code Online (Sandbox Code Playgroud)