2017-02-22 54 views
3

由於定義equals()hashCode()僅用於測試目的被認爲是代碼異味,我傾向於使用ReflectionEquals或自定義匹配器在進行單元測試時比較對象。如何比較自定義類的列表而不定義equals()和hashCode()?

但是,我不知道如何使用ReflectionEquals或自定義匹配器來比較用戶定義類的列表。

例如,如何在不定義equals()hashCode()(可能只使用ReflectionEquals或自定義匹配器)的情況下聲明以下代碼?

// When 
List<Record> actual = sut.findBySomeId(); 

// Then 
List<Record> expected = asList(
    aRecord()...build(), 
    aRecord()...build() 
); 
assertThat(expected, /* how to compare? */); 

回答

1

我要說的是,使用反射檢查equals/hashCode這本身就是另一碼味。

隨着Matcher,你必須做這樣的事情(我爲清楚起見使用了完全合格的名稱,使用import代替):這將檢查的resultvalue是一樣的,從expected之一。您可以根據需要添加儘可能多的字段。

assertThat(result, new org.hamcrest.BaseMatcher<MyObject>() { 
    public boolean matches(MyObject o) { 
    return java.lang.Objects.equals(o.getValue(), expected.getValue()); 
    } 
}); 

如果你不希望創建一個getter爲您的類的字段,然後使用默認可見性(這是什麼呢番石榴,他們註釋等領域與@VisibleForTesting)。

你也可以看看AssertJ來創建自定義和流暢的匹配器(它或多或少都是相同的)。

2

Hamcrest圖書館有很多用於對集合類型進行斷言的匹配器。特別是hasItem,hasItems,containscontainsAnyOrder匹配器,因爲它們可以自己使用匹配器(我喜歡使用TypeSafeMatcher)來測試集合中的項目。

我將離開你來決定哪一個最適合您的需求,但我會用contains我的例子:

List<Record> actual = sut.findBySomeId(); 

Record expected1 = aRecord()...build(); 
Record expected2 = aRecord()...build(); 

assertThat(actual, contains(matchingRecord(expected1), matchingRecord(expected2)); 

... 

// somewhere the test has access to it 
private Matcher<Record> matchingRecord(Record expected) { 
    return new TypeSafeMatcher<Record>() { 
     public boolean matchesSafely(Record actual) { 
      // perform tests and return result, e.g. 
      return actual.getValue() == expected.getValue(); 
     } 

     public void describeMismatchSafely(Record record, Description mismatchDescription) { 
      mismatchDescription.appendText("give a meaningful message"); 
     } 
    }; 

} 
+0

以爲我會補充說,沒有理由不能在'Matcher'的'matchesSafely'方法中使用'ReflectionEquals', –

0

我也面臨着在過去的這個問題,我完全同意你認爲只爲測試目的實施equals()hashCode()是一種代碼異味。我不使用Hamcrest圖書館,所以我會爲您提供一個自定義的解決方案,我可以在我的項目中成功使用它,並且對於它的用法我很滿意。

public static <E, A> void assertListEquals(BiConsumer<E, A> asserter, List<E> expected, List<A> actual) throws AssertionError { 
    assertEquals(
      "Lists have different sizes. Expected list: " + expected + ", actual list: " + actual, 
      expected.size(), 
      actual.size()); 

    for (int i = 0; i < expected.size(); i++) { 
     try { 
      asserter.accept(expected.get(i), actual.get(i)); 
     } catch (AssertionError e) { 
      throw e; 
     } 
    } 
} 

正如您所看到的,BiConsumer用於應用檢查兩個對象相等的邏輯。所以這個必須在測試課上實施。它的優點是您可以在比較兩個對象時定義您感興趣的類的字段。

讓我們看看它的用法: 首先我們有一個自定義類f.e.:人

public class Person { 
    private String firstName; 
    private String lastName; 
    private int age; 

    public Person(String firstName, String lastName, int age) { 
     this.firstName = firstName; 
     this.lastName = lastName; 
     this.age = age; 
    } 

    public String getFirstName() { 
     return firstName; 
    } 

    public String getLastName() { 
     return lastName; 
    } 

    public int getAge() { 
     return age; 
    } 
} 

然後讓我們看看測試方法:

@Test 
public void peopleLiveInTheVillage_findPeople_peopleAreFound() throws Exception { 
    //Arrange 
    List<Person> expectedPeople = Arrays.asList(
      new Person("Lewis", "Smith", 20), 
      new Person("Steven", "Richard", 25), 
      new Person("Richie", "Rich", 30)); 

    //Act 
    List<Person> actualPeople = sut.findPeople(); 

    //Assert 
    assertListEquals(
      (e, a) -> assertPersonEquals(e, a), 
      expectedPeople, 
      actualPeople); 
} 

private void assertPersonEquals(Person expected, Person actual) { 
    assertEquals(expected.getFirstName(), actual.getFirstName()); 
    assertEquals(expected.getLastName(), actual.getLastName()); 
    assertEquals(expected.getAge(), actual.getAge()); 
} 

由於我的測試(和TDD)的忠實粉絲,我總是寫噸的測試,所以我總是讓一個輔助方法,我包裝BiConsumer:

private void assertPeopleEqual(List<Person> expectedPeople, List<Person> actualPeople) throws AssertionError { 
    assertListEquals(
      (e, a) -> assertPersonEqual(e, a), 
      expectedPeople, 
      actualPeople); 
} 

我希望這可以幫助,歡呼!