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

背景

我正在为 MyObject类编写一个简单的 < a href = “ http://www.JUnit.org/”rel = “ noReferrer”title = “ JUnit: 用于测试驱动开发的资源”> JUnit 测试。

MyObject可以从采用 一个 href = “ http://docs.oracle.com/javase/7/docs/api/java/lang/String.html”rel = “ noReferrer”title = “ JDK: java.lang. String”> String 变量的静态工厂方法创建。

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

MyObject存在期间的任何时候,客户端都可以通过 .getComponents()方法以 网址: http://docs.oracle.com/javase/7/docs/api/java/util/List.html的形式检查它创建的参数。

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

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

  • getComponents的顺序将与为创建对象选择的顺序相同
  • 允许重复后续的 一个 href = “ http://docs.oracle.com/javase/7/docs/api/java/lang/String.html”rel = “ noReferrer”title = “ JDK: java.lang. String”> String 组件,并按顺序保留
  • null上的行为是未定义的(其他代码保证 null不会到达工厂)
  • 在对象实例化之后,无法更改组件列表

我正在编写一个简单的测试,从 一个 href = “ http://docs.oracle.com/javase/7/docs/api/java/lang/String.html”rel = “ noReferrer”title = “ JDK: java.lang. String”> 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));

提问

  • 如果构建路径中有库,那么比较这两个列表的最佳方式是 谷歌番石榴图书馆Iterables.elementsEqual()吗?this is something I have been agonizing about; should I use this helper method which goes over an Iterable<E>.. check size and then iterate running .equals().. or any other of the methods that an Internet search suggests? what's the canonical way to compare lists for unit tests?

我很乐意得到可选的见解

  • 方法测试设计合理吗? 我不是 < a href = “ http://www.JUnit.org/”rel = “ noReferrer”title = “ JUnit: 用于测试驱动开发的资源”> JUnit 方面的专家!
  • .toArray()是将 网址: http://docs.oracle.com/javase/7/docs/api/java/util/List.html转换为 E 变量的最佳方法吗?
121706 次浏览

The equals() method on your List implementation should do elementwise comparison, so

assertEquals(argumentComponents, returnedComponents);

is a lot easier.

Why not simply use List#equals?

assertEquals(argumentComponents, imapPathComponents);

Contract of List#equals:

two lists are defined to be equal if they contain the same elements in the same order.

org.junit.Assert.assertEquals() and org.junit.Assert.assertArrayEquals() do the job.

To avoid next questions: If you want to ignore the order put all elements to set and then compare: Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))

If however you just want to ignore duplicates but preserve the order wrap you list with LinkedHashSet.

Yet another tip. The trick Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two)) works fine until the comparison fails. In this case it shows you error message with to string representations of your sets that can be confusing because the order in set is almost not predictable (at least for complex objects). So, the trick I found is to wrap the collection with sorted set instead of HashSet. You can use TreeSet with custom comparator.

I prefer using Hamcrest because it gives much better output in case of a failure

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

Instead of reporting

expected true, got false

it will report

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

IsIterableContainingInOrder.contain

Hamcrest

According to the Javadoc:

Creates a matcher for Iterables that matches when a single pass over the examined Iterable yields a series of items, each logically equal to the corresponding item in the specified items. For a positive match, the examined iterable must be of the same length as the number of specified items

So the listUnderTest must have the same number of elements and each element must match the expected values in order.

  • My answer about whether Iterables.elementsEqual is best choice:

Iterables.elementsEqual is enough to compare 2 Lists.

Iterables.elementsEqual is used in more general scenarios, It accepts more general types: Iterable. That is, you could even compare a List with a Set. (by iterate order, it is important)

Sure ArrayList and LinkedList define equals pretty good, you could call equals directly. While when you use a not well defined List, Iterables.elementsEqual is the best choice. One thing should be noticed: Iterables.elementsEqual does not accept null

  • To convert List to array: Iterables.toArray is easer.

  • For unit test, I recommend add empty list to your test case.

For excellent code-readability, Fest Assertions has nice support for asserting lists

So in this case, something like:

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

Or make the expected list to an array, but I prefer the above approach because it's more clear.

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

assertTrue()/assertFalse() : to use only to assert boolean result returned

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

You want to use Assert.assertTrue() or Assert.assertFalse() as the method under test returns a boolean value.
As the method returns a specific thing such as a List that should contain some expected elements, asserting with assertTrue() in this way : Assert.assertTrue(myActualList.containsAll(myExpectedList) is an anti pattern.
It makes the assertion easy to write but as the test fails, it also makes it hard to debug because the test runner will only say to you something like :

expected true but actual is false

Assert.assertEquals(Object, Object) in JUnit4 or Assertions.assertIterableEquals(Iterable, Iterable) in JUnit 5 : to use only as both equals() and toString() are overrided for the classes (and deeply) of the compared objects

It matters because the equality test in the assertion relies on equals() and the test failure message relies on toString() of the compared objects.
As String overrides both equals() and toString(), it is perfectly valid to assert the List<String> with assertEquals(Object,Object). And about this matter : you have to override equals() in a class because it makes sense in terms of object equality, not only to make assertions easier in a test with JUnit.
To make assertions easier you have other ways (that you can see in the next points of the answer).

Is Guava a way to perform/build unit test assertions ?

Is Google Guava Iterables.elementsEqual() the best way, provided I have the library in my build path, to compare those two lists?

No it is not. Guava is not an library to write unit test assertions.
You don't need it to write most (all I think) of unit tests.

What's the canonical way to compare lists for unit tests?

As a good practice I favor assertion/matcher libraries.

I cannot encourage JUnit to perform specific assertions because this provides really too few and limited features : it performs only an assertion with a deep equals.
Sometimes you want to allow any order in the elements, sometimes you want to allow that any elements of the expected match with the actual, and so for...

So using a unit test assertion/matcher library such as Hamcrest or AssertJ is the correct way.
The actual answer provides a Hamcrest solution. Here is a AssertJ solution.

org.assertj.core.api.ListAssert.containsExactly() is what you need : it verifies that the actual group contains exactly the given values and nothing else, in order as stated :

Verifies that the actual group contains exactly the given values and nothing else, in order.

Your test could look like :

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");
}

A AssertJ good point is that declaring a List as expected is needless : it makes the assertion straighter and the code more readable :

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

And if the test fails :

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

you get a very clear message such as :

java.lang.AssertionError:

Expecting:

<["One", "Two", "Three"]>

to contain exactly (and in same order):

<["One", "Two"]>

but some elements were not expected:

<["Three"]>

Assertion/matcher libraries are a must because these will really further

Suppose that MyObject doesn't store Strings but Foos instances such as :

public class MyFooObject {


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


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

That is a very common need. With AssertJ the assertion is still simple to write. Better you can assert that the list content are equal even if the class of the elements doesn't override equals()/hashCode() while JUnit ways require that :

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"));
}

Some of the solutions make sense and I agree with them. But for me, I would use assertEquals but I would sort the two lists.

assertEquals(sortedExpectedList, sortedActualList);

It is simple and the output still gives you the diff between the actual and the expected.