如何JUnit测试两个列表 包含在同一顺序相同的元素?(How to JUnit test t

2019-07-31 17:00发布

上下文

我写一个简单的JUnit用于测试MyObject类。

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

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

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

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

换句话说,一个MyObject都记住并自曝该将其带入存在的参数列表。 有关本合同的更多细节:

  • 的顺序getComponents将是相同的作为选择的对象创建一个
  • 重复随后的字符串允许组分并保持在顺序
  • 对行为null未定义(其它代码,保证无null到达工厂)
  • 有没有办法来改变组件的对象实例化后的列表

我写一个简单的测试,创建一个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));

  • 谷歌番石榴 Iterables.elementsEqual()的最好方法,只要我有库在我的构建路径,那些两个列表比较? 这是我一直折腾有关; 我应该使用这个辅助方法,它越过了可迭代<E> 。检查大小,然后重复运行.equals() ..或任何其他的,一个互联网搜索建议的方法是什么? 什么是比较列出了单元测试的典型方式是什么?

可选的见解我很想得到

  • 是方法试验设计合理? 我不是专家的JUnit
  • .toArray()的转换的最佳途径名单<E>到E的可变参数?

Answer 1:

我更喜欢使用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匹配器匹配当单个传过来的检查可迭代产生的一系列项目,每个逻辑上等于相应项目中指定项。 对于正的比赛,该检查可迭代必须是相同长度的所规定的项目数

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



Answer 2:

为什么不直接使用List#equals

assertEquals(argumentComponents, imapPathComponents);

合同List#equals

两个列表被定义为等于如果它们包含以相同的顺序相同的元件。



Answer 3:

在你的列表实现equals()方法应该做的elementwise比较,所以

assertEquals(argumentComponents, returnedComponents);

是一个容易得多。



Answer 4:

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



Answer 5:

对于优秀的代码的可读性, 巨星断言有很好的支持断言名单

所以在这种情况下,是这样的:

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

或使预期的列表到一个数组,但我更喜欢上面的方法,因为它更清楚。

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


Answer 6:

assertTrue()/ assertFalse():只用断言返回布尔结果

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

你想用Assert.assertTrue()Assert.assertFalse()来作为测试方法返回一个boolean值。
由于该方法返回一个具体的事情,如List应该包含一些预期的元素,断言assertTrue()以这样的方式Assert.assertTrue(myActualList.containsAll(myExpectedList)是一种反模式。
它使断言容易写,但作为测试失败,这也使得它很难调试,因为测试运行,只会给你喜欢的东西说:

预计true ,但实际是false

Assert.assertEquals(Object, Object)在JUnit4或Assertions.assertIterableEquals(Iterable, Iterable)中的JUnit 5:只使用既是equals()toString()所比较的对象被overrided为类(和深)

这很重要,因为声明中的平等的测试依赖于equals()和测试失败消息依赖toString()比较的对象。
作为String覆盖两者equals()toString()它是完全有效的断言List<String>assertEquals(Object,Object) 。 而这件事情:你必须覆盖equals()中的一类,因为它的对象平等的条件下才有意义,不仅使断言更容易使用JUnit测试。
为了让断言你越有其他的方式(你可以在答案的下一个点看)。

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

是谷歌番石榴Iterables.elementsEqual()的最好方法,只要我有库在我的构建路径,那些两个列表比较?

不它不是。 番石榴是不写单元测试断言一个库。
你不需要它来写单元测试的大多数(所有我认为)。

什么是比较列出了单元测试的典型方式是什么?

作为一个很好的做法我赞成断言/匹配库。

我不能鼓励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断言仍是简单的写。 你可以更好的断言,列表内容是即使类的元素不会覆盖相等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"));
}


Answer 7:

  • 我是否回答Iterables.elementsEqual是最好的选择:

Iterables.elementsEqual足以比较2 List秒。

Iterables.elementsEqual在更一般的情况下使用,它接受更多的普通型: Iterable 。 也就是说,你甚至可以比较一个ListSet 。 (通过迭代顺序,这一点很重要)

当然ArrayListLinkedList定义equals相当不错的,你可以直接调用等于。 当你使用一个没有很好地界定名单虽然, Iterables.elementsEqual是最好的选择。 有一点应该注意: Iterables.elementsEqual不接受null

  • 为了列表转换为数组: Iterables.toArray是easer。

  • 对于单元测试,我建议加空单测试用例。



文章来源: How to JUnit test that two List contain the same elements in the same order?
标签: java junit guava