【问题标题】:Best way to unit test Collection?对集合进行单元测试的最佳方法?
【发布时间】:2011-02-24 18:11:31
【问题描述】:

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

为了执行这个断言,我编写了简单的断言 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));
}

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

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

谢谢。

【问题讨论】:

    标签: java unit-testing testing junit


    【解决方案1】:

    如果您还没有建立您的收藏,另一种选择:

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

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

    附:任何帮助避免被禁止的警告将不胜感激。

    【讨论】:

      【解决方案2】:

      我无法让jasonmp85's answer 的最后一部分按原样工作。我包括了我使用的导入,因为一些 junit 罐子为了方便而包括旧的 hamcrest 东西。这对我有用,但断言循环肯定不如 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()));
      }
      
      ...
      

      【讨论】:

      • 不需要单独的for循环,这样更简洁:for (final Foo expectedFoo : b) { assertThat(a, hasItem(new SamePropertyValuesAs(expectedFoo))); }
      【解决方案3】:

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

      例如,如果您想断言集合 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())))
          }
      }
      

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

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

      【讨论】:

      • 感谢您的信息。我想我从来没有意识到这些真的存在。 :) 但我认为它不适用于我目前的情况。我刚刚阅读了文档,似乎 equalTo() 使用 Object.equals 测试对象相等性,在我的情况下,如果可能的话,我不想使用我的 equals(..)。但我会记住这个有用的参考,以备将来使用。
      • 对,equalTo()匹配器使用equals(),但是SamePropertyValuesAs匹配两个JavaBeans的所有getter和setter。但是,如果您需要匹配私有字段或其他内容,则走这条路线就必须自己动手。
      • @jasonmp85 这段代码对你还有效吗?我正在使用 Hamcrest 1.2 获得 and (org.hamcrest.Matcher&lt;? super java.lang.Iterable&lt;java.util.Collection&lt;org.hamcrest.Matcher&lt;org.bitbucket.artbugorski.brainfuj.interpreter.InterpreterTest.Foo&gt;&gt;&gt;&gt;) in CombinableMatcher cannot be applied to (org.hamcrest.Matcher&lt;capture&lt;? super java.util.Collection&lt;? extends java.lang.Object&gt;&gt;&gt;)
      • 我赞成将我介绍给 SamePropertyValueAs,但 testBeans() 方法被 junit4.11 和 hamcrest1.3 破坏了。这两个..and 在and 上很无聊; hasItems(..) 在assertThat
      • 上面的简单 test() 方法无法为我编译(使用 Hamcrest 1.3),我得到 error: reference to hasItems is ambiguous, both method &lt;T#1&gt;hasItems(T#1...) in IsCollectionContaining and method &lt;T#2&gt;hasItems(T#2...) in JUnitMatchers match?
      【解决方案4】:

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

      ReflectionAssert.assertReflectionEquals(expectedCollection,actualCollection)
      

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

      【讨论】:

      • 这似乎是我收集断言的好方法。如果 A 扩展 B,我假设它会考虑 A 和 B 的字段,对吗?我在 API 文档中找不到它,但我想我可以测试一下。有没有办法指定一个规则来只断言来自 A 而不是 B 的字段?
      • 好吧,这太棒了......只需阅读文档,似乎我应该使用 ReflectionAssert.assertLenientEquals(..) 因为这没有考虑项目顺序。非常感谢。
      • @limc。不客气。我不确定是否可以只检查子类字段(这可能是可能的,我只是从未尝试过)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-12
      • 1970-01-01
      • 1970-01-01
      • 2011-05-15
      • 1970-01-01
      • 1970-01-01
      • 2010-09-06
      相关资源
      最近更新 更多