使用BeanMatchers测试含Instant字段的Bean的hashCode时生成唯一值失败的异常排查求助
问题描述
我最近在测试一个包含Instant字段的Bean的hashCode方法时遇到了异常,折腾了半天没搞懂原因,特来请教。
我的测试代码如下:
@Test public void testHash() { BeanMatchers.registerValueGenerator(Instant::now, Instant.class); assertThat(ClassForTest.class, hasValidBeanHashCode()); }
对应的Bean类:
class ClassForTest { private Instant instant; public Instant getInstant() { return instant; } public void setInstant(Instant instant) { this.instant = instant; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClassForTest that = (ClassForTest) o; return Objects.equals(instant, that.instant); } @Override public int hashCode() { return Objects.hash(instant); } }
使用的BeanMatchers依赖:
<dependency> <groupId>com.google.code.bean-matchers</groupId> <artifactId>bean-matchers</artifactId> <version>0.14</version> <scope>test</scope> </dependency>
每次运行都会抛出这个异常:
BeanMatchers Could not generate two distinct values after 128 attempts of type java.time.Instant
我觉得测试逻辑是对的,为啥会出现这个问题?怎么解决?
我的解答
嘿,这个问题我碰到过!原因其实很简单:
BeanMatchers的hasValidBeanHashCode()校验规则,需要生成两个字段值不同的Bean实例,来验证“不同实例的hashCode应该尽量不同”这个约定。而你注册的Instant::now作为值生成器,在测试代码快速调用的情况下,很可能因为系统时钟的精度限制(比如很多系统的时钟精度是毫秒级,甚至有些更低),导致连续调用Instant.now()返回的是同一个值——毕竟128次调用可能都在同一毫秒内完成,自然生成不了两个不同的Instant。
解决办法也很直接,自定义一个能保证生成不同Instant的生成器就行,给你几个方案:
方案1:每次生成的Instant递增一个纳秒
BeanMatchers.registerValueGenerator(() -> Instant.now().plusNanos(1), Instant.class);
这样哪怕两次调用的now()值一样,加上1纳秒后就肯定不同了。
方案2:用计数器生成绝对唯一的Instant
如果不想依赖系统时钟,也可以用计数器来生成固定递增的Instant:
AtomicLong counter = new AtomicLong(System.currentTimeMillis()); BeanMatchers.registerValueGenerator(() -> Instant.ofEpochMilli(counter.getAndIncrement()), Instant.class);
这个方式更稳定,每次生成的Instant都是严格递增的,绝对不会重复。
方案3:直接用两个固定的不同Instant值
如果只是为了通过测试,也可以直接指定两个不同的Instant作为生成器的返回值(比如第一次返回Instant.MIN,第二次返回Instant.MAX),不过这种方式灵活性稍差,但胜在简单。
总之核心就是:给BeanMatchers提供一个能稳定生成不同Instant实例的生成器,代替原来的Instant::now,就能解决这个异常啦!
备注:内容来源于stack exchange,提问作者user471011

