5

“用调评” 一体化:生成上下文数据集,改善 AI 测试生成质量

 8 months ago
source link: https://www.phodal.com/blog/use-finetune-eval-unique/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Posted by: Phodal Huang Dec. 24, 2023, 8:11 p.m.

最近,我们在围绕 AutoDev 开源插件,构建完整的端到端开源辅助编程方案。即:

  • 结合 IDE 插件微调开放二进制大语言模型(所谓 “开源”模型)。
  • 构建对应开放对应的模型与数据集
  • 构建针对于微调的开源数据工程:Unit Eval 。

简单来说,就是依旧在 Unit Eval 开源项目中设计的:“用调评”一体化(即 AI 工具-模型微调-模型评测一体化),以构建更贴合于不同组织现状的编码方案。

如何让 AI 写好测试?

在大部分编码场景中,AI 辅助单元测试的效果是相对最好的。但是这也并不简单,其中的难点在于如何构建好的上下文。

什么是好的 AI 测试上下文?

作为一个经常刷测试覆盖率,以及在 ArchGuard 中构建了测试坏味道分析检查工具的工程师,我大抵可以算得上是一个经验老道的单元测试专家。

在 AI 生成的背景之下,我们预期一个好的测试上下文,它应该包括:

  • 类的构造信息(constructor)。
  • 接口、函数的输入和输出。以使得测试能构建出正确的输入和输出
  • 测试框架的相关信息。诸如于采用的是 JUnit 4 还是 JUnit 5,使用的是哪个 Mock 框架
  • 测试代码规范。诸如于命名方式等

如下是一个经典的,用于练习的测试示例:

// ....
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");
            ....
        }
}

在这个普通的测试里,如果缺乏上述的一系列信息,那么会导致一些有意思的错误:

  • 使用 JUnit 4 来编码 JUnit 5 的测试
  • 使用错误的 Mock 框架
  • 生成的 BlogPost 构建函数是错误的
  • 直接调用 private 方法,而非绕过其它的方式
  • 测试函数的命名是不规范的,不遵循内部的开发规范。

其它的可能还有诸如于缺少测试断言等其它的测试坏味道,所以上述的信息就变得非常有必要。基于我们积累下来的单元测试与 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 中设计的三个核心原则:

  • 统一提示词(Prompt)。统一工具-微调-评估底层的提示词。
  • 代码质量管道(Pipeline)。诸如于代码复杂性、代码坏味道、测试坏味道、API 设计味道等。
  • 可扩展的质量阈。自定义规则、自定义阈值、自定义质量类型等。

基于这三个原则,再融合我们的测试经验,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 来扫描测试代码,只放入生成比较好的测试。诸如于:

  • 没有断言的测试
  • 包含 Sleep 的测试 —— 说明测试写得并不好
  • 过多调试打印信息的测试用例
  • 过多 Assert 语句

当然了,根据不同的组织信息,我们还需要添加好更多的规划。

可扩展的质量阈:函数长度控制

在测试上,我们还需要进一步控制输入的测试的质量,诸如于测试代码函数的长度。当然了,现在 Unit Eval 控制的是整个类的长度,在未来将添加对于测试函数的控制。

一体化示例:AutoDev 与 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。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK