使用Cursor来辅助编写Junit5测试类

鉴于近期AI代码编辑器Cursor比较火热,对Cursor试用并针对Junit测试类生成做了一些研究 Cursor下载官网www.cursor.com/cn

⬇️下载

下载Windows对应版本的编辑器后打开

需要登录使用(邮箱注册即可)

💵定价模式

Cursor有三种定价模式,注册即为免费模式,首次注册自动享有2周Pro版的试用,无需特别订阅

🔏隐私模式

Cursor提供了隐私模式,以防代码泄露

设置->隐私模式 若不开启隐私模式,在使用cursor过程中,cursor可能会收集代码用于模型训练

🍃编辑器的风格

打开cursor编辑器,cursor编辑器的风格类似于VSCode,插件库也主要是VScode插件库 需要安装需要使用的插件

🔨Cursor的使用

编辑器使用类似于VScode,在右侧增添了AI对话框 AI模型包含了gpt4.0等多个大模型,使用方法类似于chatgpt

📄使用案例

请结合整个工程的上下文信息,分析并为ASECommunicatorBean撰写junit测试类

让我先分析一下 ASECommunicatorBean 的相关信息。 让我们分析一下 ASECommunicatorBean 的主要功能和特点:

  1. 主要功能:
  • 作为一个通信中介Bean,负责处理与web服务器的通信
  • 提供多种数据传输方法,支持不同格式的数据交换
  • 实现了 ASECommunicator 接口
  1. 关键方法:
  • getData(DocumentImpl): 处理 XML DOM 格式的数据
  • getData(String): 处理字符串格式的数据
  • getData(byte[]): 处理字节数组格式的数据
  • getData(String, byte[]): 处理文件名和内容
  • getData(String, String): 处理文件名和字符串内容
  • getData(DocumentImpl, byte[]): 处理 DOM 和二进制内容 基于以上分析,我来编写一个完整的 JUnit 测试类:
java 复制代码
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class ASECommunicatorBeanTest extends BaseUnitTestSupport {

    @InjectMocks
    private ASECommunicatorBean communicatorBean;

    @Mock
    private RMICommunicatorProcess rmiCommunicatorProcess;

    @BeforeAll
    public static void setUp() {
        MockitoAnnotations.openMocks(ASECommunicatorBeanTest.class);
        EECacheHook.getInstance().start();
    }

    @Test
    public void testGetDataWithDocumentImpl() throws Exception {
        // 准备测试数据
        DocumentImpl inputDoc = new DocumentImpl();
        DocumentImpl expectedDoc = new DocumentImpl();

        // 模拟 RMICommunicatorProcess 的行为
        when(rmiCommunicatorProcess.getData(any(DocumentImpl.class))).thenReturn(expectedDoc);

        // 执行测试
        DocumentImpl result = communicatorBean.getData(inputDoc);

        // 验证结果
        assertNotNull(result);
        assertEquals(expectedDoc, result);
    }

    @Test
    public void testGetDataWithString() throws Exception {
        //...具体其他的实现
    }
    //...生成的其他测试方法
}

这个测试类的主要特点: 基础设置:

  • 继承 EEBaseUnitTestSupport
  • 使用 Spring Boot 测试框架
  • 使用 Mockito 进行模拟

测试用例覆盖:

  • 测试 DOM 文档处理
  • 测试字符串格式处理
  • 测试字节数组处理
  • 测试压缩数据处理
  • 测试异常情况

关键测试点:

  • 正常数据处理流程
  • 数据格式转换
  • 压缩/解压缩功能
  • 错误处理
  • 边界条件

测试方法特点:

  • 每个方法专注测试一个功能点
  • 包含正向和反向测试场景
  • 使用断言验证结果
  • 模拟外部依赖

依赖注入:

  • 使用 @InjectMocks 注入被测试类
  • 使用 @Mock 模拟依赖组件

使用说明:

  • 将此测试类放在 src/test/java/com/ejb/ctrl/ 目录下

确保项目依赖中包含必要的测试库:

  • JUnit 5
  • Mockito
  • Spring Test

运行测试:

  • 可以单独运行某个测试方法
  • 可以运行整个测试类
  • 可以通过测试套件运行

这个测试类的主要特点: 基础设置:

  • 继承 BaseUnitTestSupport
  • 使用 Spring Boot 测试框架
  • 使用 Mockito 进行模拟

测试用例覆盖:

  • 测试 DOM 文档处理
  • 测试字符串格式处理
  • 测试字节数组处理
  • 测试压缩数据处理
  • 测试异常情况

关键测试点:

  • 正常数据处理流程
  • 数据格式转换
  • 压缩/解压缩功能
  • 错误处理
  • 边界条件

测试方法特点:

  • 每个方法专注测试一个功能点
  • 包含正向和反向测试场景
  • 使用断言验证结果
  • 模拟外部依赖

依赖注入:

  • 使用 @InjectMocks 注入被测试类
  • 使用 @Mock 模拟依赖组件

使用说明:

  • 将此测试类放在 src/test/java/com/cs/eximap/ejb/ctrl/ 目录下

确保项目依赖中包含必要的测试库:

  • JUnit 5
  • Mockito
  • Spring Test

运行测试:

  • 可以单独运行某个测试方法
  • 可以运行整个测试类
  • 可以通过测试套件运行

这个测试类提供了全面的测试覆盖,包括正常场景和异常场景,有助于确保 ASECommunicatorBean 的可靠性和稳定性。这个测试类提供了全面的测试覆盖,包括正常场景和异常场景,有助于确保 ASECommunicatorBean 的可靠性和稳定性。

结构完整,无明显的编译错误,后续可以根据业务修改测试方法的测试数据。

AI生成的测试类有错怎么办?

由于目前AI的局限性,AI生成的代码并不能确保没有问题,遇到问题时,当然是再丢给AI啦

首次生成的代码verify(TransactionUtil.class, times(1)).addSQLStatement(any(), any());这段有错误,对话框提醒"你生成的代码有错误"

AI修复错误重新生成:

java 复制代码
class MicroEntpVerifyServiceTest extends BaseUnitTestSupport {

    @Mock
    private TrxBean mockTrxBean;

    @Mock
    private DocumentImpl mockDocument;

    @BeforeAll
    static void beforeAll() {
        System.setProperty("CURRENT_RUNNING_MODE", "JUNIT");
    }

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void testExecute_WithNoParamData() throws Exception {
        //...具体其他的实现
    }
    
    @Test
    void testExecute_WithFincStrategy() throws Exception {
        // 准备测试数据
        FeeDto.FeeRecord feeRecord = new FeeDto.FeeRecord();
        feeRecord.setCustId("6782023");
        feeRecord.setSubjNo("5375");

        FeeDto feeDto = new FeeDto();
        feeDto.setFeeRecords(CollectionUtil.newArrayList(feeRecord));
        feeDto.setPrdtType("68");
        feeDto.setPrdtTypeDesc("测试产品");
        feeDto.setMainRef("123");

        // 模拟参数数据
        List<Map<String, String>> ctrlParamDataList = new ArrayList<>();
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("STRATEGY_ID", "FINC");
        paramMap.put("FINC_TYPE_EX", "68");
        paramMap.put("CONTROL_TYPE", "P");
        paramMap.put("CONTROL_DESC", "测试控制");
        ctrlParamDataList.add(paramMap);

        // 模拟各种静态方法调用
        try (MockedStatic<MicroEntpVerifyUtil> mockedStatic = mockStatic(MicroEntpVerifyUtil.class);
             MockedStatic<TransactionUtil> transactionUtilMockedStatic = mockStatic(TransactionUtil.class);
             MockedStatic<DAOHelper> daoHelperMockedStatic = mockStatic(DAOHelper.class)) {

            // 模拟MicroEntpVerifyUtil.getMicroEntpCtrlParamData
            mockedStatic.when(() -> MicroEntpVerifyUtil.getMicroEntpCtrlParamData(any(), any()))
                    .thenReturn(ctrlParamDataList);

            // 模拟TransactionUtil.createTrxDocImpl
            transactionUtilMockedStatic.when(TransactionUtil::createTrxDocImpl)
                    .thenReturn(mockDocument);

            // 模拟DAOHelper.executeBatch
            daoHelperMockedStatic.when(() -> DAOHelper.executeBatch(any()))
                    .thenReturn(1);

            // 执行测试
            MicroEntpVerifyService service = new MicroEntpVerifyService(feeDto, mockTrxBean);
            MicroEntpVerifyResult result = service.execute();

            // 验证结果
            Assertions.assertNotNull(result);
            Assertions.assertTrue(result.isInvolved());
            Assertions.assertEquals(ControlTypeEnum.P, result.getControlType());
            Assertions.assertEquals("测试控制", result.getNotice());

            // 验证TransactionUtil.addSQLStatement被调用
            transactionUtilMockedStatic.verify(() -> TransactionUtil.addSQLStatement(any(), any()), times(1));
        }
    }
 
    //...生成的其他测试方法
}

主要修改内容为:

java 复制代码
// 原来的错误写法
verify(TransactionUtil.class, times(1)).addSQLStatement(any(), any());

// 修改后的正确写法
transactionUtilMockedStatic.verify(() -> TransactionUtil.addSQLStatement(any(), any()), times(1));

我已经创建了一个新的测试类,让我来解释一下这个测试类的主要特点以及与原有测试类的对比: 测试类结构:

  • 继承了BaseUnitTestSupport基类
  • 使用了@Mock注解模拟依赖对象
  • 包含了@BeforeAll和@BeforeEach设置方法
  • 提供了4个主要测试用例

主要测试场景:

  • testExecute_WithNoParamData: 测试无参数数据的情况
  • testExecute_WithFincStrategy: 测试FINC策略的情况
  • testExecute_WithDefaultStrategy: 测试默认策略的情况
  • testExecute_WithMultipleFeeRecords: 测试多条费用记录的情况

与原有测试类的对比: > 优势: 使用了Mockito进行依赖注入和静态方法模拟,使测试更加可控 每个测试用例都有清晰的注释说明测试目的 测试用例覆盖了更多的业务场景 使用了try-with-resources来管理MockedStatic资源 验证了更多的结果属性 代码结构更清晰,每个测试方法职责单一 > 不足: 原有测试类中的serviceTest()方法测试了结果列表的处理逻辑,新测试类没有覆盖这个场景 原有测试类中的updateTest()方法测试了更新操作,新测试类没有包含这个测试
测试覆盖的主要功能点:

  • 参数数据获取和处理
  • 不同策略(FINC和DEFAULT)的执行
  • 多记录处理
  • 结果验证
  • 数据库操作

改进建议:

  • 可以添加对异常场景的测试
  • 可以添加对边界条件的测试
  • 可以添加对性能相关的测试
  • 可以添加对并发场景的测试

代码质量:

  • 使用了JUnit 5的注解和断言
  • 遵循了测试代码的最佳实践
  • 提供了清晰的测试数据准备和验证步骤
  • 使用了适当的Mockito功能来模拟依赖

总的来说,新的测试类在代码质量、测试覆盖率和可维护性方面都有所提升,但还可以进一步补充一些场景的测试用例。

请弥补你的不足之处:原有测试类中的serviceTest()方法测试了结果列表的处理逻辑,新测试类没有覆盖这个场景;原有测试类中的updateTest()方法测试了更新操作,新测试类没有包含这个测试

我已经修改了测试类,主要做了以下改进: 修改了testExecute_WithResultList()测试用例: 移除了对不存在的getResultList()方法的调用 改为验证结果对象的uid字段,因为这是结果对象中实际存在的字段 保持了原有的参数数据设置,包括RESULT_LIST字段 重写了testExecute_WithUpdateOperation()测试用例: 完全重写了测试逻辑,使其与原有测试类中的updateTest()方法保持一致 使用UserSelectResult和UpdateMicroEntpAnalyser来测试更新操作 验证更新操作的执行结果 添加了必要的导入语句 保持了其他测试用例不变,因为它们都是有效的且符合现有类结构的。 这些修改使测试类更加符合实际的代码结构,同时保持了测试的完整性。每个测试用例都专注于测试特定的功能点,并且使用了适当的断言来验证结果。 主要的改进包括: 更好的代码组织 更准确的测试场景 更合理的断言验证 更好的错误处理 这些修改应该能解决之前的编译错误,同时保持了测试的有效性。

在修改过程中AI生成的代码可能出现编译错误,但是通过训练可以使其自动处理,完善 最终给出的类为

java 复制代码
class MicroEntpVerifyServiceTest extends BaseUnitTestSupport {

    @Mock
    private TrxBean mockTrxBean;

    @Mock
    private DocumentImpl mockDocument;

    @BeforeAll
    static void beforeAll() {
        System.setProperty("CURRENT_RUNNING_MODE", "JUNIT");
    }

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void testExecute_WithNoParamData() throws Exception {
        // 准备测试数据
        FeeDto.FeeRecord feeRecord = new FeeDto.FeeRecord();
        feeRecord.setCustId("6782023");
        feeRecord.setSubjNo("5375");

        FeeDto feeDto = new FeeDto();
        feeDto.setFeeRecords(CollectionUtil.newArrayList(feeRecord));
        feeDto.setMainRef("123");

        // 模拟MicroEntpVerifyUtil.getMicroEntpCtrlParamData返回空列表
        try (MockedStatic<MicroEntpVerifyUtil> mockedStatic = mockStatic(MicroEntpVerifyUtil.class)) {
            mockedStatic.when(() -> MicroEntpVerifyUtil.getMicroEntpCtrlParamData(any(), any()))
                    .thenReturn(new ArrayList<>());

            // 执行测试
            MicroEntpVerifyService service = new MicroEntpVerifyService(feeDto, mockTrxBean);
            MicroEntpVerifyResult result = service.execute();

            // 验证结果
            Assertions.assertNotNull(result);
            Assertions.assertFalse(result.isInvolved());
        }
    }

    @Test
    void testExecute_WithFincStrategy() throws Exception {
        //...具体其他的实现
    }
    //...生成的其他测试方法
}

对于复杂的业务场景的测试类的编写,Cursor在生成过程中可能出现编译错误,但是可以通过训练在生成过程中自动修复。

🗞️使用感受

在使用 Cursor 自动生成复杂 Java 类的 JUnit 单元测试时,整体体验较为高效,但也暴露出两个明显的问题:

首先,在生成过程中,Cursor 有时会调用一些并不存在的方法,导致编译错误。这类错误可能源于 AI 对代码逻辑的"过度猜测"。在处理这类问题时,Cursor 会尝试多次(通常最多25次)尝试修复编译失败,一旦仍无法解决,最终将错误信息返回并请求用户干预。比较理想的情况是,AI能够更早地识别出方法并不存在,而不是陷入重复尝试。这种"过度坚持"的修复逻辑在某些场景下反而增加了调试成本。当然,值得肯定的是,AI在后续确实可以识别该方法不存在,并自动将其移除,说明其错误反馈机制尚可,但整体效率仍有优化空间。

其次,Cursor 在自动生成测试代码时,对于已有代码中存在的 SonarQube 问题(如空指针判断、资源释放、过长方法等)无法自动处理或规避。这可能导致生成的代码依然存在质量隐患。尚不确定 Cursor 是否支持与 SonarQube 插件集成,或是否需要额外配置以提升代码质量感知能力。如果未来能实现与静态代码分析工具的联动,比如在生成测试代码前就识别出待修复的代码质量问题,或在生成测试时规避潜在警告,将大幅提升测试代码的健壮性与规范性。

补充思考:

  1. 建议引入预处理机制:Cursor 若能在生成测试代码前先静态分析待测试类的结构、依赖和可用方法,或许能有效减少编译错误,提高首轮生成的准确性。

  2. 生成策略应更"保守":在缺乏方法定义或上下文信息的情况下,是否可以设置"保守模式",优先生成可编译的测试骨架,而不是主动构造潜在不存在的方法调用。

  3. 增强对代码质量工具的兼容性:如果 Cursor 能读取 SonarQube 的分析结果,或支持 plugin 插件模式与主流质量工具协作,那将进一步提升其在大型项目中的实用性。

  4. 更加准确的指令规避问题:如果在消息框中明确提示 Cursor "请仅调用该类中已存在的方法,避免虚构调用",或可降低生成不可编译代码的概率。


相关推荐
粥里有勺糖9 小时前
用Trae做了个公众号小工具
前端·ai编程·trae
杂雾无尘10 小时前
用 Trae 打造全栈项目魔法师 - 让项目初始化不再是噩梦
aigc·openai·ai编程
量子位10 小时前
Figure 机器人分拣快递新视频曝光,网友:太像人类
llm·ai编程
量子位10 小时前
Gemini 新版蝉联竞技场榜一,但刚发布就被越狱了
ai编程
偶尔的鱼11 小时前
Cursor 1.0 正式发布:AI 编程从「助手」变「队友」,真香预警!
ai编程
志辉AI编程11 小时前
OpenAI深夜放大招!Codex颠覆AI编程
ai编程
皓子13 小时前
海狸IM桌面端:AI辅助开发的技术架构实践
前端·electron·ai编程
Mapmost13 小时前
【AI技术闲谈】AI一键生成前端代码?实测4款工具后的操作指南
前端·ai编程
我是小七呦15 小时前
😧纳尼?前端也能做这么复杂的事情了?
前端·面试·ai编程