单元测试集合的最佳方式?

时间:2021-06-02 21:46:49

I'm just wondering how folks unit test and assert that the "expected" collection is the same/similar as the "actual" collection (order is not important).

我只是想知道人们如何测试并断言“预期”集合与“实际”集合相同/相似(顺序并不重要)。

To perform this assertion, I wrote my simple assert API:-

为了执行这个断言,我编写了我的简单断言API: -

public void assertCollection(Collection<?> expectedCollection, Collection<?> actualCollection) {
    assertNotNull(expectedCollection);
    assertNotNull(actualCollection);
    assertEquals(expectedCollection.size(), actualCollection.size());
    assertTrue(expectedCollection.containsAll(actualCollection));
    assertTrue(actualCollection.containsAll(expectedCollection));
}

Well, it works. It's pretty simple if I'm asserting just bunch of Integers or Strings. It can also be pretty painful if I'm trying to assert a collection of Hibernate domains, say for example. The collection.containsAll(..) relies on the equals(..) to perform the check, but I always override the equals(..) in my Hibernate domains to check only the business keys (which is the best practice stated in the Hibernate website) and not all the fields of that domain. Sure, it makes sense to check just against the business keys, but there are times I really want to make sure all the fields are correct, not just the business keys (for example, new data entry record). So, in this case, I can't mess around with the domain.equals(..) and it almost seems like I need to implement some comparators for just unit testing purposes instead of relying on collection.containsAll(..).

嗯,它的工作原理。如果我断言只是一堆整数或字符串,这很简单。例如,如果我试图断言Hibernate域的集合,那也可能会非常痛苦。 collection.containsAll(..)依赖于equals(..)来执行检查,但我总是覆盖我的Hibernate域中的equals(..)以仅检查业务键(这是最好的做法,在Hibernate网站)而不是该域的所有字段。当然,检查业务键是有意义的,但有时我真的想确保所有字段都正确,而不仅仅是业务键(例如,新数据输入记录)。因此,在这种情况下,我无法使用domain.equals(..)并且几乎看起来我需要实现一些比较器,仅用于单元测试目的,而不是依赖于collection.containsAll(..)。

Are there some testing libraries I could leverage here? How do you test your collection?

我可以在这里使用一些测试库吗?你如何测试你的收藏?

Thanks.

谢谢。

4 个解决方案

#1


8  

If the equals method doesn't check all the fields, you can use the Unitils http://unitils.org/ ReflectionAssert class. Calling

如果equals方法不检查所有字段,则可以使用Unitils http://unitils.org/ ReflectionAssert类。调用

ReflectionAssert.assertReflectionEquals(expectedCollection,actualCollection)

will compare each element reflectively field by field (and this doesn't just apply for collections, it will work for any object).

将逐个字段地反复比较每个元素(这不仅适用于集合,它将适用于任何对象)。

#2


16  

I'm not sure what version of JUnit you're using, but recent ones have an assertThat method which takes a Hamcrest Matcher as an argument. They're composable so you can build up complex assertions about a collection.

我不确定你正在使用什么版本的JUnit,但是最近有一个使用Hamcrest Matcher作为参数的assertThat方法。它们是可组合的,因此您可以构建有关集合的复杂断言。

For instance, if you wanted to assert that a collection A contained every element in collection B, you could write:

例如,如果你想断言集合A包含集合B中的每个元素,你可以写:

import static org.junit.Assert.*;
import static org.junit.matchers.JUnitMatchers.*;
import static org.hamcrest.core.IsCollectionContaining.*;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import org.hamcrest.beans.SamePropertyValuesAs;

public class CollectionTests {

    /*
    * Tests that a contains every element in b (using the equals()
    * method of each element) and that a has the same size as b.
    */
    @Test
    public void test() {
        Collection<Foo> a = doSomething();
        Collection<Foo> b = expectedAnswer;

        assertThat(a, both(hasItems(b)).and(hasSize(b.size())));
    }

    /*
    * Tests that a contains every element in b (using introspection
    * to compare bean properties) and that a has the same size as b.
    */
    @Test
    public void testBeans() {
        Collection<Foo> a = doSomething();
        Collection<Foo> b = expectedAnswer;
        Collection<Matcher<Foo>> bBeanMatchers =
          new LinkedList<Matcher<Foo>>();

        // create a matcher that checks for the property values of each Foo
        for(Foo foo: B)
            bBeanMatchers.add(new SamePropertyValuesAs(foo));

        assertThat(a, both(hasItems(bBeanMatchers)).and(hasSize(b.size())))
    }
}

The first test just uses the equalTo() matcher on every object (which will delegate to your equals implementation). If that's not strong enough, you can use the second case, which will use getters and setters to compare every element. Finally, you can even write your own matchers. The Hamcrest package doesn't come with a matcher for matching by field (as opposed to matching bean properties), but it's trivial to write a FieldMatcher (and indeed is a good exercise).

第一个测试只是在每个对象上使用equalTo()匹配器(它将委托给你的equals实现)。如果这还不够强大,你可以使用第二种情况,它将使用getter和setter来比较每个元素。最后,您甚至可以编写自己的匹配器。 Hamcrest软件包没有匹配字段匹配(而不是匹配bean属性),但编写FieldMatcher很简单(确实是一个很好的练习)。

The Matchers are a bit odd at first, but if you follow their example of making new Matchers have a static method that returns the matcher you can do a bunch of import statics and your code basically reads like an English sentence ("assert that a both has the items in b and has the same size as b"). You can build up a pretty impressive DSL with these things and make your test code a lot more elegant.

Matchers一开始有点奇怪,但是如果你按照他们的例子来制作新的Matchers有一个返回匹配器的静态方法,你可以做一堆导入静态,你的代码基本上就像一个英文句子(“断言两者都是具有b中的项目并且具有与b“)相同的大小。您可以使用这些东西构建一个非常令人印象深刻的DSL,并使您的测试代码更加优雅。

#3


1  

Another option if you have not build your collection:

如果您尚未构建集合,则另一个选项:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
import static org.hamcrest.Matchers.is;

@Test
@SuppressWarnings("unchecked")
public void test_returnsList(){

    arrange();

    List<MyBean> myList = act();

    assertThat(myList , contains(allOf(hasProperty("id",          is(7L)), 
                                       hasProperty("name",        is("testName1")),
                                       hasProperty("description", is("testDesc1"))),
                                 allOf(hasProperty("id",          is(11L)), 
                                       hasProperty("name",        is("testName2")),
                                       hasProperty("description", is("testDesc2")))));
}

Use containsInAnyOrder if you do not want to check the order of the objects.

如果您不想检查对象的顺序,请使用containsInAnyOrder。

P.S. Any help to avoid the warning that is suppresed will be really appreciated.

附:任何帮助以避免被扼杀的警告将非常感激。

#4


0  

I couldn't get the last part of jasonmp85's answer to work as is. I included the imports I used because some junit jars include old hamcrest stuff for convenience. This works for me, but the assert loop definitely isn't as nice as if hasItems(..) worked as written in jason's answer.

我无法得到jasonmp85的最后一部分答案。我包括了我使用的进口,因为一些junit罐子包括旧的hamcrest东西以方便。这对我有用,但是assert循环肯定不像hasItems(..)那样好,就像jason的回答一样。

import org.hamcrest.Matcher;
import org.hamcrest.beans.SamePropertyValuesAs;
import org.hamcrest.collection.IsCollectionWithSize;

import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;

...

/*
* Tests that a contains every element in b (using introspection
* to compare bean properties) and that a has the same size as b.
*/
@Test
public void testBeans() {
    Collection<Foo> a = doSomething();
    Collection<Foo> b = expectedAnswer;
    Collection<Matcher<Foo>> bBeanMatchers = new LinkedList<Matcher<Foo>>();

    // create a matcher that checks for the property values of each Foo
    for(Foo foo: B)
        bBeanMatchers.add(new SamePropertyValuesAs(foo));

    // check that each matcher matches something in the list
    for (Matcher<Foo> mf : bBeanMatchers)
        assertThat(a, hasItem(mf));

    // check that list sizes match
    assertThat(a, IsCollectionWithSize.hasSize(b.size()));
}

...

#1


8  

If the equals method doesn't check all the fields, you can use the Unitils http://unitils.org/ ReflectionAssert class. Calling

如果equals方法不检查所有字段,则可以使用Unitils http://unitils.org/ ReflectionAssert类。调用

ReflectionAssert.assertReflectionEquals(expectedCollection,actualCollection)

will compare each element reflectively field by field (and this doesn't just apply for collections, it will work for any object).

将逐个字段地反复比较每个元素(这不仅适用于集合,它将适用于任何对象)。

#2


16  

I'm not sure what version of JUnit you're using, but recent ones have an assertThat method which takes a Hamcrest Matcher as an argument. They're composable so you can build up complex assertions about a collection.

我不确定你正在使用什么版本的JUnit,但是最近有一个使用Hamcrest Matcher作为参数的assertThat方法。它们是可组合的,因此您可以构建有关集合的复杂断言。

For instance, if you wanted to assert that a collection A contained every element in collection B, you could write:

例如,如果你想断言集合A包含集合B中的每个元素,你可以写:

import static org.junit.Assert.*;
import static org.junit.matchers.JUnitMatchers.*;
import static org.hamcrest.core.IsCollectionContaining.*;
import static org.hamcrest.collection.IsCollectionWithSize.*;
import org.hamcrest.beans.SamePropertyValuesAs;

public class CollectionTests {

    /*
    * Tests that a contains every element in b (using the equals()
    * method of each element) and that a has the same size as b.
    */
    @Test
    public void test() {
        Collection<Foo> a = doSomething();
        Collection<Foo> b = expectedAnswer;

        assertThat(a, both(hasItems(b)).and(hasSize(b.size())));
    }

    /*
    * Tests that a contains every element in b (using introspection
    * to compare bean properties) and that a has the same size as b.
    */
    @Test
    public void testBeans() {
        Collection<Foo> a = doSomething();
        Collection<Foo> b = expectedAnswer;
        Collection<Matcher<Foo>> bBeanMatchers =
          new LinkedList<Matcher<Foo>>();

        // create a matcher that checks for the property values of each Foo
        for(Foo foo: B)
            bBeanMatchers.add(new SamePropertyValuesAs(foo));

        assertThat(a, both(hasItems(bBeanMatchers)).and(hasSize(b.size())))
    }
}

The first test just uses the equalTo() matcher on every object (which will delegate to your equals implementation). If that's not strong enough, you can use the second case, which will use getters and setters to compare every element. Finally, you can even write your own matchers. The Hamcrest package doesn't come with a matcher for matching by field (as opposed to matching bean properties), but it's trivial to write a FieldMatcher (and indeed is a good exercise).

第一个测试只是在每个对象上使用equalTo()匹配器(它将委托给你的equals实现)。如果这还不够强大,你可以使用第二种情况,它将使用getter和setter来比较每个元素。最后,您甚至可以编写自己的匹配器。 Hamcrest软件包没有匹配字段匹配(而不是匹配bean属性),但编写FieldMatcher很简单(确实是一个很好的练习)。

The Matchers are a bit odd at first, but if you follow their example of making new Matchers have a static method that returns the matcher you can do a bunch of import statics and your code basically reads like an English sentence ("assert that a both has the items in b and has the same size as b"). You can build up a pretty impressive DSL with these things and make your test code a lot more elegant.

Matchers一开始有点奇怪,但是如果你按照他们的例子来制作新的Matchers有一个返回匹配器的静态方法,你可以做一堆导入静态,你的代码基本上就像一个英文句子(“断言两者都是具有b中的项目并且具有与b“)相同的大小。您可以使用这些东西构建一个非常令人印象深刻的DSL,并使您的测试代码更加优雅。

#3


1  

Another option if you have not build your collection:

如果您尚未构建集合,则另一个选项:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
import static org.hamcrest.Matchers.is;

@Test
@SuppressWarnings("unchecked")
public void test_returnsList(){

    arrange();

    List<MyBean> myList = act();

    assertThat(myList , contains(allOf(hasProperty("id",          is(7L)), 
                                       hasProperty("name",        is("testName1")),
                                       hasProperty("description", is("testDesc1"))),
                                 allOf(hasProperty("id",          is(11L)), 
                                       hasProperty("name",        is("testName2")),
                                       hasProperty("description", is("testDesc2")))));
}

Use containsInAnyOrder if you do not want to check the order of the objects.

如果您不想检查对象的顺序,请使用containsInAnyOrder。

P.S. Any help to avoid the warning that is suppresed will be really appreciated.

附:任何帮助以避免被扼杀的警告将非常感激。

#4


0  

I couldn't get the last part of jasonmp85's answer to work as is. I included the imports I used because some junit jars include old hamcrest stuff for convenience. This works for me, but the assert loop definitely isn't as nice as if hasItems(..) worked as written in jason's answer.

我无法得到jasonmp85的最后一部分答案。我包括了我使用的进口,因为一些junit罐子包括旧的hamcrest东西以方便。这对我有用,但是assert循环肯定不像hasItems(..)那样好,就像jason的回答一样。

import org.hamcrest.Matcher;
import org.hamcrest.beans.SamePropertyValuesAs;
import org.hamcrest.collection.IsCollectionWithSize;

import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;

...

/*
* Tests that a contains every element in b (using introspection
* to compare bean properties) and that a has the same size as b.
*/
@Test
public void testBeans() {
    Collection<Foo> a = doSomething();
    Collection<Foo> b = expectedAnswer;
    Collection<Matcher<Foo>> bBeanMatchers = new LinkedList<Matcher<Foo>>();

    // create a matcher that checks for the property values of each Foo
    for(Foo foo: B)
        bBeanMatchers.add(new SamePropertyValuesAs(foo));

    // check that each matcher matches something in the list
    for (Matcher<Foo> mf : bBeanMatchers)
        assertThat(a, hasItem(mf));

    // check that list sizes match
    assertThat(a, IsCollectionWithSize.hasSize(b.size()));
}

...