“用調評”一體化:生成上下文數據集,改善AI測試生成質量

代碼爲聘禮 2024-02-20 03:46:25

最近,我們在圍繞 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 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。

0 阅读:0

代碼爲聘禮

簡介:感謝大家的關注