简体   繁体   English

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

[英]Best way to unit test Collection?

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. 例如,如果我试图断言Hibernate域的集合,那也可能会非常痛苦。 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. collection.containsAll(..)依赖于equals(..)来执行检查,但我总是覆盖我的Hibernate域中的equals(..)以仅检查业务键(这是最好的做法,在Hibernate网站)而不是该域的所有字段。 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(..). 因此,在这种情况下,我无法使用domain.equals(..)并且几乎看起来我需要实现一些比较器,仅用于单元测试目的,而不是依赖于collection.containsAll(..)。

Are there some testing libraries I could leverage here? 我可以在这里使用一些测试库吗? How do you test your collection? 你如何测试你的收藏?

Thanks. 谢谢。

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. 我不确定你正在使用什么版本的JUnit,但是最近有一个使用Hamcrest Matcher作为参数的assertThat方法。 They're composable so you can build up complex assertions about a collection. 它们是可组合的,因此您可以构建关于集合的复杂断言。

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). 第一个测试只是在每个对象上使用equalTo()匹配器(它将委托给你的equals实现)。 If that's not strong enough, you can use the second case, which will use getters and setters to compare every element. 如果这还不够强大,你可以使用第二种情况,它将使用getter和setter来比较每个元素。 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). 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 static s 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"). Matchers一开始有点奇怪,但是如果你按照他们的例子来制作新的Matchers有一个返回匹配器的静态方法你可以做一堆import static s并且你的代码基本上读起来像一个英语句子(“断言a两者都有b中的项目,并且与b“)具有相同的大小。 You can build up a pretty impressive DSL with these things and make your test code a lot more elegant. 您可以使用这些东西构建一个非常令人印象深刻的DSL,并使您的测试代码更加优雅。

If the equals method doesn't check all the fields, you can use the Unitils http://unitils.org/ ReflectionAssert class. 如果equals方法不检查所有字段,则可以使用Unitils http://unitils.org/ ReflectionAssert类。 Calling 调用

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). 将逐个字段地反复比较每个元素(这不仅适用于集合,它将适用于任何对象)。

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

PS Any help to avoid the warning that is suppresed will be really appreciated. PS任何有助于避免被禁止的警告的帮助将非常感激。

I couldn't get the last part of jasonmp85's answer to work as is. 我无法得到jasonmp85的最后一部分答案 I included the imports I used because some junit jars include old hamcrest stuff for convenience. 我包括了我使用的进口,因为为方便起见,一些junit罐包括旧的hamcrest东西。 This works for me, but the assert loop definitely isn't as nice as if hasItems(..) worked as written in jason's answer. 这对我hasItems(..) ,但是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()));
}

...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM