如何在不抛出AssertionError的情况下获取两个对象的可读差异输出(类似AssertJ)
无需抛出异常的对象对比&可读差异方案
我们在生产环境用AssertJ做对象对比时,确实会遇到要写大量try/catch、还要丢弃堆栈的麻烦,下面几个方案可以解决这个问题:
一、AssertJ自带的无异常方案(最贴合需求)
AssertJ本身提供了SoftAssertions类,专门用来收集断言失败信息而不抛出异常,完美匹配你的场景:
// 初始化软断言实例 SoftAssertions softly = new SoftAssertions(); // 沿用你熟悉的AssertJ对比逻辑 softly.assertThat(valueB) .usingComparatorForType(BigInteger::compareTo, BigInteger.class) .isEqualToComparingFieldByField(valueA); // 获取所有失败断言(单次对比只会有一个) List<Throwable> failures = softly.errors(); if (!failures.isEmpty()) { // 直接拿到和示例一致的可读差异信息,无需处理堆栈 String diffMessage = failures.get(0).getMessage(); // 记录diffMessage即可 }
这个方案完全沿用原有AssertJ语法,不需要额外依赖,也不用手动写try/catch,直接获取差异文本。
二、标准库实现(手动反射对比)
如果不想依赖AssertJ,可以基于Java反射自己实现字段对比逻辑,虽然不如AssertJ健壮,但胜在无外部依赖:
public static String compareObjects(Object expected, Object actual) { if (expected == actual) return null; if (expected == null || actual == null) return "Expected [" + expected + "] but was [" + actual + "]"; Class<?> clazz = expected.getClass(); if (!clazz.equals(actual.getClass())) return "Expected type [" + clazz.getName() + "] but was [" + actual.getClass().getName() + "]"; StringBuilder diff = new StringBuilder("Error during validation between value A and value B:\n"); List<String> fieldNames = new ArrayList<>(); List<Object> expectedValues = new ArrayList<>(); List<Object> actualValues = new ArrayList<>(); // 遍历所有字段(可按需调整是否包含父类字段) for (Field field : clazz.getDeclaredFields()) { field.setAccessible(true); try { Object expVal = field.get(expected); Object actVal = field.get(actual); // 处理BigInteger自定义比较,可扩展其他类型 boolean equal = false; if (expVal instanceof BigInteger && actVal instanceof BigInteger) { equal = ((BigInteger) expVal).compareTo((BigInteger) actVal) == 0; } else { equal = Objects.equals(expVal, actVal); } if (!equal) { fieldNames.add(field.getName()); expectedValues.add(expVal); actualValues.add(actVal); } } catch (IllegalAccessException e) { diff.append("Failed to access field ").append(field.getName()).append(": ").append(e.getMessage()).append("\n"); } } if (!fieldNames.isEmpty()) { diff.append("Expecting values:\n <").append(expectedValues).append(">\n"); diff.append("in fields:\n <").append(fieldNames).append(">\n"); diff.append("but were:\n <").append(actualValues).append(">\n"); diff.append("in <").append(actual).append(">.\n"); diff.append("Comparison was performed on all fields"); return diff.toString(); } return null; }
调用这个方法就能得到类似AssertJ格式的差异文本,缺点是需要自己维护类型比较逻辑,比如集合、嵌套对象等场景要额外扩展。
三、第三方工具类方案(JSON对比)
如果对象结构适合转JSON,可以用JSON对比工具生成差异,比如JSONAssert(需引入依赖):
ObjectMapper mapper = new ObjectMapper(); String expectedJson = mapper.writeValueAsString(valueA); String actualJson = mapper.writeValueAsString(valueB); // 对比JSON并获取差异信息 String diff = JSONAssert.compareJSON(expectedJson, actualJson, false).getMessage();
这种方案的差异是JSON格式的,可读性良好,适合复杂嵌套对象场景,但需要额外引入Jackson和JSONAssert依赖。
内容的提问来源于stack exchange,提问作者evanjdooner
相关产品推荐
相关产品推荐

