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

如何无重复代码生成Java不可变类的测试数据实例?

针对Lombok @Value不可变类的单元测试对象生成优化方案

问题描述

我有一个用Lombok的@Value注解标记的大型不可变类,需要对它的验证逻辑做单元测试。测试时要生成多个和有效实例仅个别属性不同的对象,但不想每次都重复写完整的对象构造代码。如果是可变类,我只要生成一个有效实例再修改对应属性就行,但不可变类只能每次全量构造,这种写法太冗余。试过创建结构相同的可变类再转不可变对象,但这个方案不太理想,有没有更合理的参数化实现方式?

示例代码

@Value
class MyClass{
    String a;
    String b;
    String c;
    ...
}

// 可变类的理想写法(不可用):
getValid() {
    return new MyClass("a","b","c"...);
}

testA() {
    var obj = getValid();
    obj.a = "invalid";
    // ... 执行测试
}

// 不可变类当前冗余写法:
testA() {
    var obj = new MyClass("invalid","b","c"...); // 希望避免这种写法
    // ... 执行测试
}

解决方案

1. 利用Lombok @With注解生成属性拷贝方法

Lombok的@With注解会为每个属性生成withXxx()方法,调用后返回新的不可变实例,仅修改指定属性,其余属性与原对象一致,完全契合不可变对象的设计原则。

修改原类

@Value
@With // 添加该注解
class MyClass{
    String a;
    String b;
    String c;
    // ... 其他属性
}

测试代码优化

// 定义全局有效实例生成方法
private MyClass getValidInstance() {
    return new MyClass("a", "b", "c"...);
}

@Test
void testInvalidA() {
    MyClass invalidObj = getValidInstance().withA("invalid");
    // 执行验证逻辑测试,例如断言抛出ValidationException
}

@Test
void testInvalidB() {
    MyClass invalidObj = getValidInstance().withB("invalid");
    // 执行测试
}

2. 使用Lombok @Builder配合toBuilder功能

给@Value类添加@Builder(toBuilder = true)注解,可从已有实例生成Builder,修改指定属性后构建新实例,适合需要同时修改多个属性的场景。

修改原类

@Value
@Builder(toBuilder = true) // 开启toBuilder功能
class MyClass{
    String a;
    String b;
    String c;
    // ... 其他属性
}

测试代码优化

private MyClass getValidInstance() {
    return MyClass.builder().a("a").b("b").c("c")...build();
}

@Test
void testInvalidA() {
    MyClass invalidObj = getValidInstance().toBuilder().a("invalid").build();
    // 执行测试
}

@Test
void testInvalidBAndC() {
    // 同时修改多个属性的场景
    MyClass invalidObj = getValidInstance().toBuilder()
        .b("invalidB")
        .c("invalidC")
        .build();
    // 执行测试
}

3. 自定义测试数据构建器+JUnit参数化测试

如果需要批量测试多种无效场景,可结合JUnit 5参数化测试和自定义构建器,减少重复代码并统一管理测试数据。

自定义测试构建器

class MyClassTestBuilder {
    // 默认赋值为有效属性值
    private String a = "a";
    private String b = "b";
    private String c = "c";
    // ... 其他属性默认值

    // 定义各种无效场景的方法
    public MyClassTestBuilder withInvalidA() {
        this.a = "invalid";
        return this;
    }

    public MyClassTestBuilder withNullB() {
        this.b = null;
        return this;
    }

    public MyClassTestBuilder withEmptyC() {
        this.c = "";
        return this;
    }

    // 构建最终的MyClass实例
    public MyClass build() {
        return new MyClass(a, b, c...);
    }
}

参数化测试实现

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;

class MyClassValidationTest {

    // 提供测试场景:每个元素对应一种无效构建方式
    static Stream<MyClass> invalidInstances() {
        return Stream.of(
            new MyClassTestBuilder().withInvalidA().build(),
            new MyClassTestBuilder().withNullB().build(),
            new MyClassTestBuilder().withEmptyC().build()
        );
    }

    @ParameterizedTest
    @MethodSource("invalidInstances")
    void testValidation(MyClass invalidObj) {
        // 执行统一的验证逻辑测试,例如断言验证失败
        assertThrows(ValidationException.class, () -> validator.validate(invalidObj));
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:17:12