You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Groovy合并两个Builder类:单元测试实现默认值与覆写

问题

我有一个大型Customer类,使用Groovy的@Builder注解生成Builder类,运行正常。编写单元测试时,希望先定义包含所有默认值的Customer对象,再通过Builder传入覆写值,且不想手动传入字段名(易出错)。

现有Builder类代码:

@Builder(builderStrategy = ExternalStrategy, forClass = Customer.class, includeSuperProperties = true)
class CustomerBuilder {

}

测试用的TestDataBuilder类接收CustomerBuilder参数,希望合并默认Builder与传入的覆写Builder,生成包含默认值和覆写值的Customer对象,请问如何实现?

TestDataBuilder代码:

class TestDataBuilder {
    public TestDataBuilder withDefaultCustomerOverrides(CustomerBuilder customerBuilder) {
        CustomerBuilder customerBuilderDefault = new CustomerBuilder()
                .modifiedDateTime(LocalDateTime.now())
                .custName("Test Client")
        
        // 需合并两个Builder
        
        return customerBuilderDefault.build() 
    }
}


TestDataBuilder testDataBuilder = new TestDataBuilder()
        testDataBuilder.withDefaultCustomerOverrides(new CustomerBuilder().custName("Custom Test Client"))
解决方案

核心思路是提取覆写Builder中已设置的非空属性,覆盖到默认Builder的对应属性上,利用Groovy的动态特性自动处理所有字段,无需手动指定。

修改后的TestDataBuilder实现如下:

import java.time.LocalDateTime

class TestDataBuilder {
    public Customer withDefaultCustomerOverrides(CustomerBuilder customerBuilder) {
        // 初始化默认Builder并设置默认值
        CustomerBuilder customerBuilderDefault = new CustomerBuilder()
                .modifiedDateTime(LocalDateTime.now())
                .custName("Test Client")

        // 遍历覆写Builder的所有属性,将非空值覆盖到默认Builder
        customerBuilder.metaClass.properties.each { prop ->
            // 跳过Groovy内置的元属性,只处理业务字段
            if (!['class', 'metaClass'].contains(prop.name)) {
                def overrideValue = prop.getProperty(customerBuilder)
                if (overrideValue != null) {
                    customerBuilderDefault.setProperty(prop.name, overrideValue)
                }
            }
        }

        return customerBuilderDefault.build()
    }
}

// 测试调用示例
TestDataBuilder testDataBuilder = new TestDataBuilder()
Customer finalCustomer = testDataBuilder.withDefaultCustomerOverrides(
        new CustomerBuilder().custName("Custom Test Client")
)

说明

  • 利用Groovy的metaClass.properties自动遍历所有字段,彻底避免手动写字段名的出错风险
  • 仅覆盖非空值,确保默认值不会被覆写Builder中的空值覆盖
  • 支持父类属性(因为Builder注解设置了includeSuperProperties = true),无需额外处理

优化方案(针对Builder内部结构)

如果Groovy生成的Builder内部用特定容器存储已设置的字段(比如$fields Map),可以直接操作这个容器提升效率:

// 先通过println(customerBuilder.dump())确认内部存储字段的属性名
customerBuilder.$fields.each { key, value ->
    if (value != null) {
        customerBuilderDefault.$fields[key] = value
    }
}

内容的提问来源于stack exchange,提问作者Ambuj Jauhari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 08:13:39