2015-01-06 58 views
0

簡短的問題:我有我想在我的JUnit測試中比較值對象的情況。這些值對象只有幾個不同類型的字段(但主要是原始類型)。我從一個XML文件創建一個對象,另一個來自我的數據庫的數據集。JUnit - 比較值對象

我解決了它,通過覆蓋我的值對象類中的equals方法,然後通過assertEquals(o1,o2)比較這兩個方法。 我想知道這個任務是否有另一種解決方案。也許我不必寫的解決方案等於每個價值等級的方法(有幾個...)

我已經試驗了Hamcrest,但並不真正成功。我嘗試assertThat(o2,is(equalTo(o1))); 如果對象相同,則JUnit測試成功,但如果不成功,測試不會失敗,但會以異常退出(不要認爲它應該如何工作,對吧?)

我也想到了一些東西用反射自動比較類的所有字段,但我不知道如何開始。

你有什麼建議可以解決這個問題嗎?

+1

equals是正確的方法,或者您可以使用getter比較這些對象的單個字段。 – SMA

+1

這個SO帖子可能包含一些解決方案:http://stackoverflow.com/q/12147297/1291150 –

+1

值對象應該總是重寫equals方法,因爲說一個對象是一個值對象就是說可以存在一個不同的方法對象相當於此對象,equals方法指示哪些其他對象相同。 – Raedwald

回答

1

您實際上提到了3種最流行的技術: 1.實施「好」等於方法,並在單元測試中使用它。 2.使用一系列assertEquals() 3.使用assertThat()

我個人使用的所有這些技術發現他們都非常有用的;這個選擇取決於具體的要求。有時候我創建了comaprison工具類,它包含我的值對象的一系列assert方法。這有助於在不同的單元測試中重用斷言代碼。

根據我的經驗,對所有值對象實施好的equals(),hashCode()toString()是個好主意。這並不難。您可以使用Apache公用程序中的EqualsBuilder,HashCodeBuilderToSringBuilder或Java 7中引入的Object實用程序。這取決於您。

如果性能不是問題(恕我直言糾正99.999%的真實應用程序)使用基於反射的建設者(從Apache公共)。這使得實現非常簡單並且無需維護。

在大多數情況下,在單元測試中使用equals()已經足夠了。但如果不好,請根據您的選擇使用assertThat()assertEquals()的組合。

+0

謝謝你們的回答,我認爲EqualsBuilder正是我正在尋找的=)問題解決了,謝謝你的補充解釋。 –

0

像AlexR的回答指出這是落實好equals()hashCode()toString()方法是一個好主意和Apache公地對這個偉大的幫手,如果表現還不是最重要的問題(和單元測試它不是)。

我在過去類似的測試要求,並有我不只是想聽到值對象是不同的,但也看到其性質是不同(而不僅僅是第一,但所有)。我創建(使用Spring的BeanWrapper接口),一個助手來做到這一點,它是可供選擇:https://bitbucket.org/fhoeben/hsac-test,並允許您撥打UnitTestHelper.assertEqualsWithDiff(T expected, T actual)在你單元測試

/** 
* Checks whether expected and actual are equal, and if not shows which 
* properties differ. 
* @param expected expected object. 
* @param actual actual object 
* @param <T> object type. 
*/ 
public static <T> void assertEqualsWithDiff(T expected, T actual) { 
    Map<String, String[]> diffs = getDiffs(null, expected, actual); 

    if (!diffs.isEmpty()) { 
     StringBuilder diffString = new StringBuilder(); 
     for (Entry<String, String[]> diff : diffs.entrySet()) { 
      appendDiff(diffString, diff); 
     } 
     fail(diffs.size() + " difference(s) between expected and actual:\n" + diffString); 
    } 
} 

private static void appendDiff(StringBuilder diffString, Entry<String, String[]> diff) { 
    String propertyName = diff.getKey(); 
    String[] value = diff.getValue(); 
    String expectedValue = value[0]; 
    String actualValue = value[1]; 

    diffString.append(propertyName); 
    diffString.append(": '"); 
    diffString.append(expectedValue); 
    diffString.append("' <> '"); 
    diffString.append(actualValue); 
    diffString.append("'\n"); 
} 

private static Map<String, String[]> getDiffs(String path, Object expected, Object actual) { 
    Map<String, String[]> diffs = Collections.emptyMap(); 
    if (expected == null) { 
     if (actual != null) { 
      diffs = createDiff(path, expected, actual); 
     } 
    } else if (!expected.equals(actual)) { 
     if (actual == null 
       || isInstanceOfSimpleClass(expected)) { 
      diffs = createDiff(path, expected, actual); 
     } else if (expected instanceof List) { 
      diffs = listDiffs(path, (List) expected, (List) actual); 
     } else { 
      diffs = getNestedDiffs(path, expected, actual); 
     } 
     if (diffs.isEmpty() && !(expected instanceof JAXBElement)) { 
      throw new IllegalArgumentException("Found elements that are not equal, " 
        + "but not able to determine difference, " 
        + path); 
     } 
    } 
    return diffs; 
} 

private static boolean isInstanceOfSimpleClass(Object expected) { 
    return expected instanceof Enum 
      || expected instanceof String 
      || expected instanceof XMLGregorianCalendar 
      || expected instanceof Number 
      || expected instanceof Boolean; 
} 

private static Map<String, String[]> listDiffs(String path, List expectedList, List actualList) { 
    Map<String, String[]> diffs = new LinkedHashMap<String, String[]>(); 
    String pathFormat = path + "[%s]"; 
    for (int i = 0; i < expectedList.size(); i++) { 
     String nestedPath = String.format(pathFormat, i); 
     Object expected = expectedList.get(i); 
     Map<String, String[]> elementDiffs; 
     if (actualList.size() > i) { 
      Object actual = actualList.get(i); 
      elementDiffs = getDiffs(nestedPath, expected, actual); 
     } else { 
      elementDiffs = createDiff(nestedPath, expected, "<no element>"); 
     } 
     diffs.putAll(elementDiffs); 
    } 
    for (int i = expectedList.size(); i < actualList.size(); i++) { 
     String nestedPath = String.format(pathFormat, i); 
     diffs.put(nestedPath, createDiff("<no element>", actualList.get(i))); 
    } 
    return diffs; 
} 

private static Map<String, String[]> getNestedDiffs(String path, Object expected, Object actual) { 
    Map<String, String[]> diffs = new LinkedHashMap<String, String[]>(0); 
    BeanWrapper expectedWrapper = getWrapper(expected); 
    BeanWrapper actualWrapper = getWrapper(actual); 
    PropertyDescriptor[] descriptors = expectedWrapper.getPropertyDescriptors(); 
    for (PropertyDescriptor propertyDescriptor : descriptors) { 
     String propertyName = propertyDescriptor.getName(); 
     Map<String, String[]> nestedDiffs = 
       getNestedDiffs(path, propertyName, 
         expectedWrapper, actualWrapper); 
     diffs.putAll(nestedDiffs); 
    } 
    return diffs; 
} 

private static Map<String, String[]> getNestedDiffs(
     String path, 
     String propertyName, 
     BeanWrapper expectedWrapper, 
     BeanWrapper actualWrapper) { 
    String nestedPath = propertyName; 
    if (path != null) { 
     nestedPath = path + "." + propertyName; 
    } 
    Object expectedValue = getValue(expectedWrapper, propertyName); 
    Object actualValue = getValue(actualWrapper, propertyName); 
    return getDiffs(nestedPath, expectedValue, actualValue); 
} 

private static Map<String, String[]> createDiff(String path, Object expected, Object actual) { 
    return Collections.singletonMap(path, createDiff(expected, actual)); 
} 

private static String[] createDiff(Object expected, Object actual) { 
    return new String[] {getString(expected), getString(actual)}; 
} 

private static String getString(Object value) { 
    return String.valueOf(value); 
} 

private static Object getValue(BeanWrapper wrapper, String propertyName) { 
    Object result = null; 
    if (wrapper.isReadableProperty(propertyName)) { 
     result = wrapper.getPropertyValue(propertyName); 
    } else { 
     PropertyDescriptor propertyDescriptor = wrapper.getPropertyDescriptor(propertyName); 
     Class<?> propertyType = propertyDescriptor.getPropertyType(); 
     if (Boolean.class.equals(propertyType)) { 
      String name = StringUtils.capitalize(propertyName); 
      Object expected = wrapper.getWrappedInstance(); 
      Method m = ReflectionUtils.findMethod(expected.getClass(), "is" + name); 
      if (m != null && m.getReturnType().equals(Boolean.class)) { 
       result = ReflectionUtils.invokeMethod(m, expected); 
      } else { 
       throw new IllegalArgumentException(createErrorMsg(wrapper, propertyName)); 
      } 
     } else { 
      throw new IllegalArgumentException(createErrorMsg(wrapper, propertyName)); 
     } 
    } 
    return result; 
} 

private static String createErrorMsg(BeanWrapper wrapper, String propertyName) { 
    return propertyName + " can not be read on: " + wrapper.getWrappedClass(); 
} 

private static <T> BeanWrapper getWrapper(T instance) { 
    BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(instance); 
    wrapper.setAutoGrowNestedPaths(true); 
    return wrapper; 
}