鉴于近期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 的主要功能和特点:
- 主要功能:
- 作为一个通信中介Bean,负责处理与web服务器的通信
- 提供多种数据传输方法,支持不同格式的数据交换
- 实现了 ASECommunicator 接口
- 关键方法:
- 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 插件集成,或是否需要额外配置以提升代码质量感知能力。如果未来能实现与静态代码分析工具的联动,比如在生成测试代码前就识别出待修复的代码质量问题,或在生成测试时规避潜在警告,将大幅提升测试代码的健壮性与规范性。
补充思考:
-
建议引入预处理机制:Cursor 若能在生成测试代码前先静态分析待测试类的结构、依赖和可用方法,或许能有效减少编译错误,提高首轮生成的准确性。
-
生成策略应更"保守":在缺乏方法定义或上下文信息的情况下,是否可以设置"保守模式",优先生成可编译的测试骨架,而不是主动构造潜在不存在的方法调用。
-
增强对代码质量工具的兼容性:如果 Cursor 能读取 SonarQube 的分析结果,或支持 plugin 插件模式与主流质量工具协作,那将进一步提升其在大型项目中的实用性。
-
更加准确的指令规避问题:如果在消息框中明确提示 Cursor "请仅调用该类中已存在的方法,避免虚构调用",或可降低生成不可编译代码的概率。