org.assertj.core.api.ObjectAssert.usingRecursiveComparison() 配置不用于子对象

如何解决org.assertj.core.api.ObjectAssert.usingRecursiveComparison() 配置不用于子对象

我们的团队正在使用 Kotlin 和 Springboot 构建使用 Google Cloud Spanner 作为数据存储区的网络服务。

Spanner 有一个很酷的特性,叫做 commit timestamps,它记录一行在指定列中提交到数据库的时间。

这个特性的问题在于它使得编写单元测试变得困难。特别是,我想在测试中编写如下所示:

@Test
fun saveAndLoadTest() {
    val id = UUID.randomUuid().toString()
    val expected = Foo(id)
    dao.save(expected)

    val actual = dao.load(id)
    assertThat(actual).isEqualTo(expected)
}

但是如果 Foo 有一个由 Spanner 的提交时间戳填充的字段,这将不起作用,因为当 actualexpected 进行比较时,该字段会有所不同。

@Table("Foo")
data class Foo (

    @PrimaryKey
    val id: String,// the value of this field will differ before and after the object is written to the database
    @Column(spannerCommitTimestamp = true)
    val createdTimestamp: Timestamp = Timestamp.now()
)

为了解决这个问题,我编写了一个扩展函数来配置比较 Foo 的两个实例的方式。它看起来像这样:

fun <T> ObjectAssert<T>.isEqualIgnoringTimestamps(other: T): ObjectAssert<T> {
    this.usingRecursiveComparison()
        .ignoringFieldsMatchingRegexes("createdTimestamp.*")
        .isEqualTo(other)
    return this
}

如果我更新单元测试以使用我的扩展函数,则断言通过,因为名称以 createdTimestamp 开头的任何字段都将被忽略:

@Test
fun saveAndLoadTest() {
    val id = UUID.randomUuid().toString()
    val expected = Foo(id)
    dao.save(expected)

    val actual = dao.load(id)

    // at this point,we're basically only checking that the `id` fields are the same
    assertThat(actual).isEqualIgnoringTimestamps(expected)
}

这很好,但似乎 org.assertj.core.api.ObjectAssert.usingRecursiveComparison() 配置不用于子对象。这意味着如果我把 Foo 复杂化一点,扩展函数就会停止工作:

@Table("Foo")
data class Foo (

    @PrimaryKey
    val id: String,// the "recursive" comparison rules won't be applied to this child object
    val child: Foo,@Column(spannerCommitTimestamp = true)
    val createdTimestamp: Timestamp = Timestamp.now()
)

现在单元测试失败并显示如下错误消息:

Expecting:
  <Foo(id=adfsikp87r6yu0hwy4pyg8oi9,child=Foo(id=cs6ne8m8tilcbhxq3s4ejxn5n,child=null,createdTimestamp=2021-06-18T20:08:37.142614000Z),createdTimestamp=2021-06-18T20:08:37.142614000Z)>
to be equal to:
  <Foo(id=adfsikp87r6yu0hwy4pyg8oi9,createdTimestamp=2021-06-18T20:08:37.352000000Z),createdTimestamp=2021-06-18T20:08:37.352000000Z)>
when recursively comparing field by field,but found the following difference:

field/property 'child' differ:
- actual value   : Foo(id=cs6ne8m8tilcbhxq3s4ejxn5n,createdTimestamp=2021-06-18T20:08:37.352000000Z)
- expected value : Foo(id=cs6ne8m8tilcbhxq3s4ejxn5n,createdTimestamp=2021-06-18T20:08:37.142614000Z)

The recursive comparison was performed with this configuration:
- the fields matching the following regexes were ignored in the comparison: createdTimestamp.*
- these types were compared with the following comparators:
  - java.lang.Double -> DoubleComparator[precision=1.0E-15]
  - java.lang.Float -> FloatComparator[precision=1.0E-6]
- actual and expected objects and their fields were compared field by field recursively even if they were not of the same type,this allows for example to compare a Person to a PersonDto (call strictTypeChecking(true) to change that behavior).

这里的问题似乎是在比较过程中没有忽略 child.createdTimestamp 字段,即使我已经告诉比较器忽略名为 createdTimestamp 的字段。当然,它们在顶层被忽略——只是不在子对象中。

请注意,如果我调整递归比较配置以忽略基于对象类型而不是名称的字段,则会出现同样的问题:

fun <T> ObjectAssert<T>.isEqualIgnoringTimestamps(other: T): ObjectAssert<T> {
    this.usingRecursiveComparison()
        .ignoringFieldsOfTypes(Timestamp::class.java)
        .isEqualTo(other)
    return this
}

无论哪种方式,createdTimestamp 字段在具有对象中都会被忽略,但在 has-a 对象中不会被忽略。

所以,抛开所有这些解释:我应该如何处理这个问题?

  • 是否有更好的方法来配置递归比较,以便将配置应用于子对象(我本来认为是这种情况,因为它应该是递归,但我想我错了)?
  • 在使用提交时间戳时,是否有更好的模式可用于单元测试?我想我可以覆盖 .equals(...) 上的 .hashCode()Foo 方法,这样 createdTimestamp 字段被忽略,但随后我将测试代码泄漏到生产代码中,这使得我觉得恶心

tl;dr: 我的数据库在提交时在我的对象上填充一个创建的时间戳。这使得编写单元测试变得棘手,因为对象在保存时会发生变化。我该如何解决这个问题?

解决方法

我认为您的问题来自两件事的结合:

  • 使用旧版本的 AssertJ,在递归比较中默认情况下不会忽略覆盖的等于
  • 没有使用正确的正则表达式

错误表明 child 字段不相等,这向我表明您的 Foo 类具有覆盖的 equals 方法(可能来自数据类)并且您从 3.17.0 开始使用 AssertJ Core 版本 equals,递归比较也不会使用它,而是比较它的字段(注意,默认情况下总是比较根对象逐个字段)。 如果您坚持使用旧版本,只需使用 ignoringAllOverriddenEquals

然后第二个问题是您的正则表达式没有按照您的预期执行,而不是 createdTimestamp.*,请尝试 .*createdTimestamp

这是为什么?字段由它们从根对象(即被测对象)的位置表示,因此当您指定 createdTimestamp.* 时,它意味着以 createdTimestamp 开头的根 对象 的任何字段,它并不意味着任何 createdTimestamp 字段。

在您的示例中,它匹配 Foo.createdTimestamp,因为 Foo 是要比较的根类型,但它不匹配 Foo.child.createdTimestamp,要忽略后者,您必须指定 child\.createdTimestamp ( \. 用于正则表达式不将 . 解释为任何字符)。

这并不能真正解决您的问题,因为现在 child.child.createdTimestamp 不会被忽略,但这是正则表达式很方便的地方,.*createdTimestamp 可以完成这项工作,因为它意味着:“任何以 {{1} 结尾的字段位置}}”。

相关文档在这里:

我想文档可以显示更多关于字段正则表达式用法及其解释方式的示例。 WDYT?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res