ChatGPT生成单元测试实践(Golang)

前言

目前gpt本质上是续写,所以在待测函数定义清晰的情况下,单元测试可以适当依赖它进行生成。

收益是什么:

  1. 辅助生成测试用例&测试代码,降低单元测试编写的心智成本
  2. 辅助code review,帮助发现代码显式/潜在问题

本文测试环境:

  • gpt: gpt-3.5-turbo
  • go:go 1.17

本文实践场景:企业微信美图鉴赏机器人


生成单元测试的工作流如下:

  1. 选定你的待测函数
  2. 分析函数的依赖:结构体定义、repo依赖、repo interface抽象(用fx或者wire等依赖注入框架的话会方便很多)
  3. 组织prompt,准备施法
  4. 吟唱魔法,得到输出单元测试代码
  5. copy到ide里面缝缝补补,与预期出入太多的话,调整prompt重来/要求gpt按照某个标准重写(比如:请按照testCases的写法重写组织测试数据)
  6. 运行测试用例

话不多说,先上基础prompt:

写下列写下列代码中[待测函数名]函数的单测,要求覆盖全部逻辑。 
断言使用require包。 
测试用例以testCases的形式输出。

下面的部分,我按照工作流的繁杂程度,简单区分了下easy/normal/hard情况。


Part1 easy:单个函数,无复杂依赖

单个函数,并且外部依赖都是开源库,那么直接把代码贴上去就行。

这里我们用图片压缩的独立函数举例:
(prompt前摇咒语 + 代码片段)

写下列代码中CompressPicture函数的单测,要求覆盖全部逻辑。
断言使用require包。
测试用例以testCases的形式输出。// CompressPicture the picture by resizing it to the specified width.
func CompressPicture(picData []byte, width uint) ([]byte, error) {// Decode the input image.oldBuffer := bytes.NewBuffer(picData)inputImg, _, err := image.Decode(oldBuffer)if err != nil {return nil, err}// Resize the image to the specified width.outputImg := resize.Resize(width, 0, inputImg, resize.Lanczos3)// Encode the output image as PNG and write it to a buffer.newBuffer := new(bytes.Buffer)err = png.Encode(newBuffer, outputImg)if err != nil {return nil, err}// Return the compressed image as a byte slice.return newBuffer.Bytes(), nil
}

ChatGPT输出的代码块如下:

func TestCompressPicture(t *testing.T) {// Setup test dataimgData, err := os.ReadFile("test-image.png")if err != nil {t.Fatalf("failed to read test image file: %v", err)}testCases := []struct {name  stringwidth uint}{{"compress with a valid width", 500},{"compress with an invalid width", 0},}for _, tc := range testCases {t.Run(tc.name, func(t *testing.T) {// Invoke the function being tested.compressedData, err := CompressPicture(imgData, tc.width)// Check the result.if err != nil {t.Fatalf("unexpected error: %v", err)}if len(compressedData) == 0 {t.Fatalf("compressed data is empty")}create, err := os.Create("test-image-compressed.png")if err != nil {return}defer create.Close()_, err = create.Write(compressedData)})}
}

放到ide里,完美运行,说明简单函数的单元测试生成还是很强的~

pass


Part2 normal :里面有一些外部import

有外部定义引入的时候,最好把外部定义直接贴上(不涉及mock)。

这里举例的函数出现了外部依赖 entity.ArchiveWithData等定义,所以最好要加上外部定义在输入中:

写下列代码中ConvertArchiveWithDataToBotTextMsg函数的单测,要求覆盖全部逻辑。
断言使用require包。
测试用例以testCases的形式输出。// ConvertArchiveWithDataToBotTextMsg 将画作稿件信息转换为企微机器人文本消息
func ConvertArchiveWithDataToBotTextMsg(data *entity.ArchiveWithData) *entity.BotMsgReq {var MentionedList []stringproxyUrl := data.Info.Urls.OriginalrawPixivUrl := fmt.Sprintf("https://www.pixiv.net/artworks/%d", data.Info.Pid)txt := &entity.BotText{Content:       fmt.Sprintf("proxy图源:%s\npixiv图源:%s", proxyUrl, rawPixivUrl),MentionedList: MentionedList,}postText := &entity.BotMsgReq{MsgType: entity.BotMsgText,Text:    txt,}return postText
}一些导入的定义如下:
type BotText struct {Content       string   `json:"content"`MentionedList []string `json:"mentioned_list"`
}
type BotMsgReq struct {MsgType BotMsgType `json:"msgtype"`Text    *BotText   `json:"text,omitempty"`
}
const (BotMsgText  BotMsgType = "text"
)type ArchiveWithData struct {Info ArchiveData []byte
}

下面是生成的代码,经过少量修改后可以直接运行:
(因为少输入了一个结构体的定义,导致它猜测了一些字段)

func Test_ConvertArchiveWithDataToBotTextMsg(t *testing.T) {testCases := []struct {name     stringdata     *entity.ArchiveWithDataexpected *entity.BotMsgReq}{{name: "regular data",data: &entity.ArchiveWithData{Info: entity.Archive{Pid: 123456,Urls: entity.PicUrl{Original: "https://example.com/image.jpg",},},Data: []byte{},},expected: &entity.BotMsgReq{MsgType: entity.BotMsgText,Text: &entity.BotText{Content: "proxy图源:https://example.com/image.jpg\npixiv图源:https://www.pixiv.net/artworks/123456",},},},{name: "data with special characters",data: &entity.ArchiveWithData{Info: entity.Archive{Pid: 789012,Urls: entity.PicUrl{Original: "https://example.com/image(with)special&characters.jpg",},},Data: []byte{},},expected: &entity.BotMsgReq{MsgType: entity.BotMsgText,Text: &entity.BotText{Content: "proxy图源:https://example.com/image(with)special&characters.jpg\npixiv图源:https://www.pixiv.net/artworks/789012",},},},}for _, tc := range testCases {t.Run(tc.name, func(t *testing.T) {result := ConvertArchiveWithDataToBotTextMsg(tc.data)require.Equal(t, tc.expected, result)})}
}

小修小补之后顺利运行


Part3 hard:对外部repo进行mock(gomock举例)

外部依赖越多,prompt template的构建可能就越复杂
ps.实际上gomonkey它也可以写
外部依赖repo的部分,要求gpt使用gomock进行对应repo的mock即可。
外部定义最好进行剪枝。

输入:

写下列代码中getSeTuDescSlice函数的单测,要求覆盖全部逻辑。
断言使用require包。
测试用例以testCases的形式输出。
对于m.repoSeTu.GetArchiveInfoSlice的调用,使用gomock进行mock处理,假设已经有mock文件。func (m *CronTab) getSeTuDescSlice(ctx context.Context, seTuConfig config.SeTuConfig) (entity.QueryResult, error) {queryResSlice, err := m.repoSeTu.GetArchiveInfoSlice(ctx, &entity.Query{R18:   seTuConfig.R18,Num:   1,Tag:   seTuConfig.Tags,Size:  seTuConfig.PicSize,Proxy: seTuConfig.Proxy,})if err != nil {return entity.QueryResult{}, err}return queryResSlice, nil
}下面是外部依赖的定义:
type SeTuConfig struct {SetuApiUrl string   `yaml:"setu_api_url"`QWAuth     []string `yaml:"qw_auth"`CronStr    string   `yaml:"cron_str"`R18        int64    `yaml:"r_18"`Tags       []string `yaml:"tags"`PicSize    []string `yaml:"pic_size"`Proxy      string   `yaml:"proxy"`
}// QueryResult 请求结果
type QueryResult struct {Error        string    `json:"error"`ArchiveSlice []Archive `json:"data"`//picPaths     []string
}

输出:

假设已经有mock文件:// mock_repoSetu 是通过mockgen自动生成的,用于模拟repoSetu接口的mock文件func TestCronTab_getSeTuDescSlice(t *testing.T) {testCases := []struct{name stringseTuConfig config.SeTuConfiggetArchiveInfoSliceRes entity.QueryResultgetArchiveInfoSliceErr errorwantErr bool}{{name: "success",seTuConfig: config.SeTuConfig{R18: 0,Tags: []string{"tag1", "tag2"},PicSize: []string{"large"},Proxy: "",},getArchiveInfoSliceRes: entity.QueryResult{ArchiveSlice: []entity.Archive{{Urls: []string{"pic1.url", "pic2.url"},Title: "pic1",Artists: []string{"artist1"},},},},getArchiveInfoSliceErr: nil,wantErr: false,},{name: "error",seTuConfig: config.SeTuConfig{R18: 1,Tags: []string{"tag3"},PicSize: []string{"small", "medium"},Proxy: "",},getArchiveInfoSliceRes: entity.QueryResult{},getArchiveInfoSliceErr: errors.New("get archive info slice error"),wantErr: true,},}for _, tc := range testCases {t.Run(tc.name, func(t *testing.T) {ctx := context.Background()// 创建mock对象mockRepoSeTu := mock_repoSetu.NewMockRepoSeTu(ctrl)m := &CronTab{repoSeTu: mockRepoSeTu}// 设置mock对象返回值mockRepoSeTu.EXPECT().GetArchiveInfoSlice(ctx, &entity.Query{R18: tc.seTuConfig.R18,Num: 1,Tag: tc.seTuConfig.Tags,Size: tc.seTuConfig.PicSize,Proxy: tc.seTuConfig.Proxy,}).Return(tc.getArchiveInfoSliceRes, tc.getArchiveInfoSliceErr)res, err := m.getSeTuDescSlice(ctx, tc.seTuConfig)if tc.wantErr {require.Error(t, err)} else {require.NoError(t, err)require.Equal(t, tc.getArchiveInfoSliceRes, res)}})}
}测试用例解释:
测试用例"success"模拟了getSeTuDescSlice调用成功的情况,
设置查询条件R18=0,Tags="tag1", "tag2",PicSize="large",期望返回多条图片信息;
测试用例"error"模拟了getSeTuDescSlice调用失败的情况,
设置查询条件R18=1,Tags="tag3",PicSize="small", "medium",期望返回错误信息。

生成的代码太长,几点总结:

  1. gomock代码可以正常生成,但是外部repo定义最好清晰明了,可以直接把interface定义贴上去,会被认知到。
  2. 粘贴到ide中,发现主要问题是包名导入问题,简单修改即可

生成的代码有一些报错:
有些报错

修改引入的类型名后即可:
小修小补

修改后运行成功:
run!

如果需要生成后人工修改量更低的话,需要更精确的上下文信息(包名、函数定义、描述等)


一些痛点

  1. ChatGPT单次对话有长度限制,所以一次尽量只进行一个函数的单元测试生成。
  2. 需要外部 repo mock的待测函数,需要的上下文很多,并且引入的无关字段需要剪枝避免污染输入
  3. 一些复杂的私有包,看起来是比较不好支持的(尝试过在prompt中教会他,但是效果不好)
  4. 一些复杂逻辑和边界情况,ai可能口是心非(写的用例和表述不一致,比如经典的20%*20%=400%,但是他知道这是错的)
  5. 对于分层不是很清晰的项目,对函数/repo打桩可能很困难,这个时候要描述清楚就比较困难了,gomonkey可能比较适用

其他用法

  1. 写好测试方法,让gpt帮助扩充用例集
  2. code review,下面的代码就是gpt给出的代码建议
帮我优化下面的代码// picCompress Modify size to compress pictures.
func picCompress(picData []byte) (newPicData []byte, err error) {oldBuf := bytes.NewBuffer(picData)pic, _, err := image.Decode(oldBuf)if err != nil {return}newPic := resize.Resize(uint(pic.Bounds().Dx()/2), 0, pic, resize.Lanczos3)var newBuf bytes.Buffererr = png.Encode(&newBuf, newPic)if err != nil {return}newPicData, err = ioutil.ReadAll(&newBuf)if err != nil {return}return
}
您好!下面是一个优化版的代码,主要改进在于:
减少对象的创建,避免额外的内存分配和垃圾回收。
增加错误检查,避免可能的错误被忽略。
以下是优化版代码:
// picCompress Modify size to compress pictures.
func picCompress(picData []byte) ([]byte, error) {reader := bytes.NewReader(picData)pic, _, err := image.Decode(reader)if err != nil {return nil, err}newPic := resize.Resize(uint(pic.Bounds().Dx()/2), 0, pic, resize.Lanczos3)var writer bytes.Buffererr = png.Encode(&writer, newPic)if err != nil {return nil, err}return writer.Bytes(), nil
}尝试减少了对象的创建,使用了 bytes.Reader 代替了 bytes.NewBuffer 和 ioutil.ReadAll。
在处理图片过程中,使用 bytes.Buffer 代替了 newBuf 这个额外的对象。
同时也将函数的返回值简化,只返回处理后的 []byte 和可能的错误,避免了空值的返回。最后,增加错误检查,保证代码健壮性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/3384.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT - 训练ChatGPT 自主生成Prompt

文章目录 训练ChatGPT 自主生成Prompt例子 训练ChatGPT 自主生成Prompt I want you to act as a ChatGPT prompt generator, I will send a topic, you have to generate a ChatGPT prompt based on the content of the topic, the prompt should start with “I want you to …

ChatGPT 生成的代码比你写的更不安全

出品 | OSC开源社区(ID:oschina2013) ChatGPT 是 OpenAI 发布的大语言模型聊天机器人,可以根据用户的输入生成文本,其中包括代码。不过加拿大魁北克大学的研究人员发现,ChatGPT 生成的代码往往存在严重的安全问题&…

1.1 ChatGPT背后的关键技术-预训练

1. ChatGPT的含义 ChatGPT的GPT对应的英文分别为Generative Pre-trained Transformer,意思是用到Transformer网络架构的生成式预训练模型。其中预训练也叫做自监督学习(Self-supervised Learning)或者是大模型(Foundation Model&…

从语言模型到ChatGPT,大模型训练全攻略

文|python 前言 你是否想过,为什么ChatGPT能够如此火爆呢?我认为这主要是因为ChatGPT提供了好玩、有用的对话式交互功能,能够为用户提供帮助,而不仅仅是依靠“大”模型的魅力。毕竟,GPT-3在2020年就已经推出…

训练ChatGPT的必备资源:语料、模型和代码库完全指南

文|python 前言 近期,ChatGPT成为了全网热议的话题。ChatGPT是一种基于大规模语言模型技术(LLM, large language model)实现的人机对话工具。但是,如果我们想要训练自己的大规模语言模型,有哪些…

CHAT GPT 训练流程 无标题】

伊桑CHAT GPT 训练流程 第一步:监督学习 收集人工编写的期望模型是如何输出的数据集, ​​​​并使用其来训练一个生成模型(GPT3.5-based) ​ 第二步:训练奖励模型 收集人工标注的模型多个输出之间的排序数据集…

借助ChatGPT爆火,股价暴涨又暴跌后,C3.ai仍面临巨大风险

来源:猛兽财经 作者:猛兽财经 C3.ai的股价 作为一家人工智能技术提供商,C3.ai(AI)的股价曾在2021年初随着炒作情绪的增加,达到了历史最高点,但自那以后其股价就下跌了90%,而且炒作情…

谷歌版ChatGPT灾难级发布,市值一夜狂跌7000亿,熬夜网友:退钱!

看完谷歌的最新直播,我感觉,微软被“诈骗”了。 谷歌CEO劈柴哥前脚刚发文说要推出ChatGPT竞品Bard,又有各种消息暗示2月8日晚上的发布会会有“新消息”。 不到24小时,微软就连夜上线了ChatGPT版搜索。 微软CEO纳德拉还非常重视…

ChatGPT已经杀疯了...

来源:机器之心 关于 AI 的问题,可以直接问 AI。 OpenAI 新上线的 ChatGPT 可谓是火爆出圈,这个对话模型可以回答后续问题,承认错误,挑战不正确的前提,还能帮你修改代码中的 bug…… 只要和它聊上几句&#…

尴尬!谷歌版 ChatGPT 全球首秀“大翻车”,市值狂跌 7000 亿

整理 | 郑丽媛 出品 | CSDN(ID:CSDNnews) 昨日,微软宣布用 ChatGPT 改写 Bing、Edge,使其市值飙涨 5450 亿元——有了这个“珠玉”在前,加之谷歌刚官宣了其 ChatGPT 竞品 Bard,人们不由期待今日…

如何在ChatGPT中使用Prompt提示词 - 入门篇

本文导航 文章目录 什么是Prompt?Prompt的核心四个要点使用建议 Prompt的使用范例重新描述所给的文本内容突破ChatGPT的字数限制 总结 什么是Prompt? Prompt中文名提示词,指人工智能(AI)语言模型中的一个输入,它是一个简短的文…

ChatGPT中文版写周报【AI实用技巧】

写周报是打工人的日常操作,但又是相对固定和枯燥的工作。正确使用ChatGPT能轻松愉快地写周报。下面给大家举例说明: ChatGPT中文站https://ai-cn.co 问:我是程序员,负责维护公司商城,帮我写一份周报,不少于…

爆火的ChatGPT太强了!改周报、写代码、改bug,网友:谷歌搜索或GG,Stack Overflow被取代了!...

点击“开发者技术前线”,选择“星标” 让一部分开发者看到未来 来自:机器之心 OpenAI 新上线的 ChatGPT 可谓是火爆出圈,这个对话模型可以回答后续问题,承认错误,挑战不正确的前提,还能帮你修改代码中的 bu…

【用AI写周报,“卷死”同事】打造一款自动生成周报的微信小程序

文章目录 前言步骤1:创建账号步骤2:创建一个微信小程序并配置API。步骤3:在微信开发者工具中创建一个新的微信小程序项目步骤4:创建ChatGPT API云函数步骤5:创建UI界面步骤6:创建发送邮件的云函数步骤7&…

【ChatGPT】帮你写周报,这是实在太干货了,老板都看不下去了

说到写周报不少小伙伴会觉得是一件头疼的事情,一周摸鱼了不到7天,周报咋写,掰指头都能数清的事在邮件中更是寥寥数字,自己看着不舒服,老板看着更是头大,要不要搞掉他的想法油然而生; ChatGPT可谓…

职场神器:只需三分钟,让ChatGPT帮我写周报

说到周报,尽管周报的目的是为了提高团队合作和项目进展的透明度,但很多职场员工都觉得是件麻烦事。 本来忙碌了一周到周五的下午,工作完成了,可以下班去吃饭,去嗨皮了,结果临走前还得写一份“每周工作报告…

提升效率,使用ChatGPT的轻松撰写日报和周报

日报和周报是办公生活中不可或缺的部分,它们有助于记录工作进展、分享关键信息和与团队保持沟通。但是,有时写作这些报告可能会变得繁琐和耗时。在本文中,我们将介绍如何利用ChatGPT,一个强大的自然语言处理模型,提高写…

【ChatGPT进阶】如何使用ChatGPT写周报?

作为打工人,真的很讨厌写周报。每次都要绞尽脑汁的想措辞,既要总结工作内容,还要进行润色,要突出自己的能力和工作结果,还要弱化做的不好的地方。每一个字都要消耗一大堆的脑细胞。 Chatgpt的出现真的是职场人的福音&…

ChatGPT 使用 拓展资料:大模型训练为什么这么难

ChatGPT 使用 拓展资料:大模型训练为什么这么难 一些LLMs的省内存方法 https://zhuanlan.zhihu.com/p/616858352

如何训练个人的ChatGpt4

如何在自己的计算机上安装类似 ChatGPT 的个人 AI 并在没有互联网的情况下运行它 个人 AI 的“第一台 PC”时刻 这是个人AI的“第一台PC”时刻,随之而来的是限制,就像在车库里生产第一台Apple 1一样。你是先驱。今天,任何人都可以使用私人和…