最近,我们在围绕 AutoDev 开源插件,构建完整的端到端开源辅助编程方案。即:
简单来说,就是依旧在 Unit Eval 开源项目中设计的:“用调评”一体化(即 AI 工具-模型微调-模型评测一体化),以构建更贴合于不同组织现状的编码方案。
在大部分编码场景中,AI 辅助单元测试的效果是相对最好的。但是这也并不简单,其中的难点在于如何构建好的上下文。
作为一个经常刷测试覆盖率,以及在 ArchGuard 中构建了测试坏味道分析检查工具的工程师,我大抵可以算得上是一个经验老道的单元测试专家。
在 AI 生成的背景之下,我们预期一个好的测试上下文,它应该包括:
如下是一个经典的,用于练习的测试示例:
// ....
import org.junit.jupiter.api.Test;
@SpringBootTest
@AutoConfigureMockMvc
class BlogControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private BlogRepository blogRepository;
@Test
public void should_return_correct_blog_information_when_post_item() throws Exception {
BlogPost mockBlog = new BlogPost("Test Title", "Test Content", "Test Author");
....
}
}
在这个普通的测试里,如果缺乏上述的一系列信息,那么会导致一些有意思的错误:
其它的可能还有诸如于缺少测试断言等其它的测试坏味道,所以上述的信息就变得非常有必要。基于我们积累下来的单元测试与 IDE 插件经验,便需要考虑一体化的工程思路。
依照我们设计的 “一大一中一微” 的三模型体系,为了解决测试代码的生成问题,我们采用了 DeepSeek 6.7B 模型作为代码生成模型基础模型,以满足代码补全、测试生成两个高频高响应速度的需求。而为了在 AutoDev 中为了提升生成结果的准确度,使用静态代码分析生成更精准的上下文。其中包含了:
如下是在 AutoDev 中精简化后的 Prompt 示例:
Write unit test for following code.
${context.testFramework}
${context.coreFramework}
${context.testSpec}
${context.related_model}
```${context.language}
${context.selection}
```
而在我们测试了一些开源模型之后,发现理解 prompt 以及上下文的能力是有限的,甚至可能无法理解,以至于生成不了测试代码。为此,我们就需要围绕于测试提示词的上下文,构造微调数据集。
在 Unit Eval 中设计的三个核心原则:
基于这三个原则,再融合我们的测试经验,Unit Eval 中便有了测试生成的数据工程能力。然而,这并非一件简单事情,在测试这个场景之下,我们可以再看看如何实现。
在 AutoDev 中,会从 Project 中读取依赖管理工具中的 LibraryData
进而构建出对应的测试框架等信息。如下所示:
You are working on a project that uses Spring MVC,Spring WebFlux,JDBC to build RESTful APIs.
This project uses JUnit 5, you should import `org.junit.jupiter.api.Test` and use `@Test` annotation.
...
为了让模型能更好地理解对应的代码指令,在对应的测试数据集中,也需要构建对应的框架信息出来。在 Unit Eval 中会先调用 ArchGuard SCA 的分析工具,从中解析中依赖列表,进而生成的上下文信息。
而为了更好的结合的管理生成质量,我们还是需要控制一下数据集中的质量。因此,我们使用 ArchGuard Rule 来扫描测试代码,只放入生成比较好的测试。诸如于:
当然了,根据不同的组织信息,我们还需要添加好更多的规划。
在测试上,我们还需要进一步控制输入的测试的质量,诸如于测试代码函数的长度。当然了,现在 Unit Eval 控制的是整个类的长度,在未来将添加对于测试函数的控制。
根据不同的微调场景,如基于内部代码库生成数据、让开源模型理解指令,所需要的数据集大小是不一样的。在这里的场景,是让开源模型能更好地理解 AutoDev 的指令。
结合上述的原则,我们构建了新的 Unit Eval 测试数据集:https://github.com/unit-mesh/unit-eval/releases/tag/v0.2.0 ,并进行了微调。
如下是,结合微调生成的测试用例示例:
@Test
public void testCreateBlog() {
BlogPost blogDto = new BlogPost("title", "content", "author");
when(blogRepository.save(blogDto)).thenReturn(blogDto);
BlogPost blog = blogService.createBlog(blogDto);
assertEquals("title", blog.getTitle());
assertEquals("content", blog.getContent());
assertEquals("author", blog.getAuthor());
}
虽然,生成的测试构建函数都是正确的,测试也是可运行的。但是,在这里,我们可以发现一个明显的问题:生成的函数名没有符合规范。
前面在 prompt 中要求的是类似于 should_return_correct_blog_information_when_post_item
方式命名的,这也会导致后续生成的测试方法都失去对应的准确性。因此,我们需要基于上述的原则,重新检查是否符合我们的命名规范,再进行微调。
在总结并编写这篇文章的时候,还发现了一些提示词不合理之处,并更正错误。诸如于:框架的提示词、命名方式等等。如下是更新完后的上下文信息:
- Test class should be named `snake_case`.
- You are working on a project that uses Spring Boot, Spring Boot Web
- This project uses JUnit, assertj, mockito, Spring Boot Test, Spring Test to test code.
当然了,还需要依旧测试的类型进一步演进。
与编写一个可以用的 AI 辅助编码工具,如何持续演进整体的架构更有挑战。
PS:更详细可以参见:https://github.com/unit-mesh/unit-eval 项目的 README。