使用Kotlin copy()复制对象时assertEquals断言偶发失败
问题根因
这个问题和Kotlin copy方法的实现逻辑无关,是两个问题叠加导致的偶发失败:
- 你用的
openjdk:11.0.3-jre-slim存在已知的时钟读取bug:在Linux容器的cgroup资源限制环境下,JVM读取系统时钟时会偶发出现±1ms的精度偏差,这个问题在11.0.4及后续的OpenJDK 11补丁版本中才被修复。本地开发环境因为时钟源、JDK补丁版本和CI环境不一致,不会触发这个偏差,所以测试永远能通过。 - 你的测试断言逻辑本身存在脆弱点:时间戳字段属于动态生成值,哪怕没有JDK bug,数据库存储时的精度截断、持久层框架的字段自动填充逻辑、JDBC驱动的类型转换,都可能导致同一业务操作产生的两个时间值存在亚毫秒到毫秒级的差值,直接做全对象硬相等判断本身就不稳定。
你观察到的1ms差值,本质是JDBC驱动从结果集读取updatedAt字段做时间类型转换时,触发了JDK的时钟bug,导致读回的时间值和预期值差了1毫秒——你以为copy是按引用传递同一个Instant对象,但实际上你从数据库读回的newPlaylistFromDb本身就是经过JDBC、持久层框架多层转换后的对象,不是你最初插入时的那个实例,转换过程中引入的1ms偏差在有bug的JDK版本上被放大成了偶发错误。
解决方案
按优先级从高到低处理即可:
- 修复环境层面的bug:直接替换CI使用的JDK镜像,不要用存在已知bug的11.0.3版本,升级到
openjdk:11.0.20-jre-slim或更高的11 LTS补丁版本,从环境层面消除时钟读取偏差。 - 优化测试断言逻辑:不要对包含动态时间字段的实体做全对象硬相等断言,对时间字段单独做容差判断:
// 先断言所有确定的静态字段完全匹配 assertEquals(returnedItem.id, newItem.id) assertEquals(0, newItem.index) // 其余业务字段的相等断言省略 // 时间字段单独判断,允许10ms以内的合理误差,覆盖时钟、数据库、驱动转换带来的精度差 val timeDiff = Duration.between(newPlaylistFromDb.updatedAt, newItem.updatedAt).abs() assertTrue(timeDiff.toMillis() < 10) - 测试环境固定时钟源:如果项目里用了
@LastModifiedDate、@PrePersist这类自动填充时间的逻辑,在测试中统一注入固定时钟,所有时间生成都基于固定时钟返回静态值,从根源上消除动态时间带来的不确定性:// 测试用固定时钟,永远返回预设的固定时间 val testClock = Clock.fixed(Instant.parse("2022-06-20T15:50:45Z"), ZoneOffset.UTC) // 业务代码中所有Instant.now()替换为Instant.now(testClock),测试时注入testClock即可 - 统一时间字段精度:检查数据库中时间字段的定义,统一设置为
timestamp(3)(毫秒精度),和JavaInstant类型的默认精度对齐,避免数据库存储时的截断/四舍五入带来的额外偏差。
内容的提问来源于stack exchange,提问作者Javi Rodriguez
相关产品推荐
相关产品推荐

