您好,登錄后才能下訂單哦!
這篇文章主要介紹“Golang怎么使用ChatGPT生成單元測試”,在日常操作中,相信很多人在Golang怎么使用ChatGPT生成單元測試問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Golang怎么使用ChatGPT生成單元測試”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
生成單元測試的工作流如下:
選定你的待測函數(shù)
分析函數(shù)的依賴:結(jié)構(gòu)體定義、repo依賴、repo interface抽象(用fx和wire框架依賴注入的話會方便很多)
組織prompt,準備施法
吟唱魔法,得到輸出單元測試代碼
copy到ide里面縫縫補補,與預期出入太多的話,調(diào)整prompt重來/要求gpt按照某個標準重寫(比如:請按照testCases的寫法重寫組織測試數(shù)據(jù))
運行測試用例
單個函數(shù),并且外部依賴都是開源庫,那么直接把代碼貼上去就行。
這里我們用圖片壓縮的獨立函數(shù)舉例: (prompt前搖咒語 + 代碼片段)
寫下列代碼中CompressPicture函數(shù)的單測,要求覆蓋全部邏輯。 斷言使用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 data imgData, err := os.ReadFile("test-image.png") if err != nil { t.Fatalf("failed to read test image file: %v", err) } testCases := []struct { name string width 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里,完美運行,說明簡單函數(shù)的單元測試生成還是很強的~
有外部定義引入的時候,最好把外部定義直接貼上(不涉及mock)。
這里舉例的函數(shù)出現(xiàn)了外部依賴 entity.ArchiveWithData等定義,所以最好要加上外部定義在輸入中:
寫下列代碼中ConvertArchiveWithDataToBotTextMsg函數(shù)的單測,要求覆蓋全部邏輯。 斷言使用require包。 測試用例以testCases的形式輸出。 // ConvertArchiveWithDataToBotTextMsg 將畫作稿件信息轉(zhuǎn)換為企微機器人文本消息 func ConvertArchiveWithDataToBotTextMsg(data *entity.ArchiveWithData) *entity.BotMsgReq { var MentionedList []string proxyUrl := data.Info.Urls.Original rawPixivUrl := 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 Archive Data []byte }
下面是生成的代碼,經(jīng)過少量修改后可以直接運行:(因為少輸入了一個結(jié)構(gòu)體的定義,導致它猜測了一些字段)
func Test_ConvertArchiveWithDataToBotTextMsg(t *testing.T) { testCases := []struct { name string data *entity.ArchiveWithData expected *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) }) } }
外部依賴越多,prompt template的構(gòu)建可能就越復雜
ps.實際上gomonkey它也可以寫
外部依賴repo的部分,要求gpt使用gomock進行對應repo的mock即可。
外部定義最好進行剪枝。
輸入:
寫下列代碼中getSeTuDescSlice函數(shù)的單測,要求覆蓋全部邏輯。 斷言使用require包。 測試用例以testCases的形式輸出。 對于m.repoSeTu.GetArchiveInfoSlice的調(diào)用,使用gomock進行mock處理,假設已經(jīng)有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 請求結(jié)果 type QueryResult struct { Error string `json:"error"` ArchiveSlice []Archive `json:"data"` //picPaths []string }
輸出:
假設已經(jīng)有mock文件: // mock_repoSetu 是通過mockgen自動生成的,用于模擬repoSetu接口的mock文件 func TestCronTab_getSeTuDescSlice(t *testing.T) { testCases := []struct{ name string seTuConfig config.SeTuConfig getArchiveInfoSliceRes entity.QueryResult getArchiveInfoSliceErr error wantErr 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() // 創(chuàng)建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調(diào)用成功的情況, 設置查詢條件R18=0,Tags="tag1", "tag2",PicSize="large",期望返回多條圖片信息; 測試用例"error"模擬了getSeTuDescSlice調(diào)用失敗的情況, 設置查詢條件R18=1,Tags="tag3",PicSize="small", "medium",期望返回錯誤信息。
生成的代碼太長,幾點總結(jié):
gomock代碼可以正常生成,但是外部repo定義最好清晰明了,可以直接把interface定義貼上去,會被認知到。
粘貼到ide中,發(fā)現(xiàn)主要問題是包名導入問題,簡單修改即可
生成的代碼有一些報錯:
修改引入的類型名后即可:
修改后運行成功:
如果需要生成后人工修改量更低的話,需要更精確的上下文信息(包名、函數(shù)定義、描述等)
ChatGPT單次對話有長度限制,所以一次盡量只進行一個函數(shù)的單元測試生成。
需要外部 repo mock的待測函數(shù),需要的上下文很多,并且引入的無關字段需要剪枝避免污染輸入
一些復雜的私有包,看起來是比較不好支持的(嘗試過在prompt中教會他,但是效果不好)
一些復雜邏輯和邊界情況,ai可能口是心非(寫的用例和表述不一致,比如經(jīng)典的20%*20%=400%,但是他知道這是錯的)
對于分層不是很清晰的項目,對函數(shù)/repo打樁可能很困難,這個時候要描述清楚就比較困難了,gomonkey可能比較適用
寫好測試方法,讓gpt幫助擴充用例集
code review,下面的代碼就是gpt給出的代碼建議
幫我優(yōu)化下面的代碼 // 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.Buffer err = png.Encode(&newBuf, newPic) if err != nil { return } newPicData, err = ioutil.ReadAll(&newBuf) if err != nil { return } return }
您好!下面是一個優(yōu)化版的代碼,主要改進在于: 減少對象的創(chuàng)建,避免額外的內(nèi)存分配和垃圾回收。 增加錯誤檢查,避免可能的錯誤被忽略。 以下是優(yōu)化版代碼: // 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.Buffer err = png.Encode(&writer, newPic) if err != nil { return nil, err } return writer.Bytes(), nil } 嘗試減少了對象的創(chuàng)建,使用了 bytes.Reader 代替了 bytes.NewBuffer 和 ioutil.ReadAll。 在處理圖片過程中,使用 bytes.Buffer 代替了 newBuf 這個額外的對象。 同時也將函數(shù)的返回值簡化,只返回處理后的 []byte 和可能的錯誤,避免了空值的返回。最后,增加錯誤檢查,保證代碼健壯性。
到此,關于“Golang怎么使用ChatGPT生成單元測試”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。