简短的问题:我想要在JUnit测试中比较值对象。那些值对象只有几个不同类型的字段(但主要是原始类型)。我从xml文件创建一个对象,另一个从我的数据库创建数据集。
我通过覆盖我的值对象类中的equals方法解决了它,然后通过assertEquals(o1,o2)比较这两个方法。 我想知道是否有另一种解决方案可以完成这项任务。也许我不需要为每个值类编写等于方法的解决方案(有一些......)
我已经尝试过Hamcrest,但并没有真正成功。我试过断言(o2,是(equalTo(o1))); 如果对象相等,则JUnit测试成功,但如果没有,则测试不会失败,但会以异常退出(不要认为它应该如何工作,对吗?)
我还想过用反射来自动比较类的所有字段,但我不知道如何开始。
你有没有建议如何以最优雅的方式解决这个问题?
答案 0 :(得分:1)
你实际上提到了3种最流行的技巧:
1.实现“好”等于方法并从单元测试中使用它。
2.使用assertEquals()
系列
3.使用assertThat()
我个人使用了所有这些技术,发现它们都很有用;选择取决于具体要求。有时我创建了包含一系列assert
方法的comaprison实用程序类,用于我的值对象。这有助于在不同的单元测试中重用断言代码。
根据我的经验,为所有价值对象实施好equals()
,hashCode()
和toString()
是个好主意。这不是太难。您可以使用来自Java公共中引入的EqualsBuilder
的Apache公共或实用程序的HashCodeBuilder
,ToSringBuilder
和Object
。这取决于您。
如果性能不是问题(IMHO对99.999%的实际应用程序是正确的)使用基于反射的构建器(来自apache commons)。这使得实现非常简单且无需维护。
在大多数情况下,在单元测试中使用equals()
就足够了。但是,如果不好,请根据您的选择使用assertThat()
或assertEquals()
的血管。
答案 1 :(得分: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;
}
答案 2 :(得分:0)
库Hamcrest 1.3 Utility Matchers有一个特殊的匹配器,它使用反射而不是等号。
assertThat(obj1, reflectEquals(obj2));