开发者

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:-

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 开发者_JAVA技巧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(..).

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. 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:

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).

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.


If the equals method doesn't check all the fields, you can use the Unitils http://unitils.org/ ReflectionAssert class. 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.

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


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.

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

...
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜