最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang使用ChatGPT生成單元測試實(shí)踐

 更新時(shí)間:2023年03月19日 11:31:55   作者:StuGRua  
這篇文章主要為大家介紹了Golang使用ChatGPT生成單元測試實(shí)踐詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

目前gpt本質(zhì)上是續(xù)寫,所以在待測函數(shù)函數(shù)定義清晰的情況下,單元測試可以適當(dāng)依賴它進(jìn)行生成。

收益是什么:

  • 輔助生成測試用例&測試代碼,降低單元測試編寫的心智成本
  • 輔助code review,幫助發(fā)現(xiàn)代碼顯式/潛在問題

本文測試環(huán)境:

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

本文實(shí)踐場景:企業(yè)微信美圖鑒賞機(jī)器人

生成單元測試的工作流如下:

  • 選定你的待測函數(shù)
  • 分析函數(shù)的依賴:結(jié)構(gòu)體定義、repo依賴、repo interface抽象(用fx和wire框架依賴注入的話會(huì)方便很多)
  • 組織prompt,準(zhǔn)備施法
  • 吟唱魔法,得到輸出單元測試代碼
  • copy到ide里面縫縫補(bǔ)補(bǔ),與預(yù)期出入太多的話,調(diào)整prompt重來/要求gpt按照某個(gè)標(biāo)準(zhǔn)重寫(比如:請(qǐng)按照testCases的寫法重寫組織測試數(shù)據(jù))
  • 運(yùn)行測試用例

話不多說,先上基礎(chǔ)prompt:

寫下列寫下列代碼中[待測函數(shù)名]函數(shù)的單測,要求覆蓋全部邏輯。 

斷言使用require包。 

測試用例以testCases的形式輸出。

下面的部分,我按照工作流的繁雜程度,簡單區(qū)分了下easy/normal/hard情況。

Part1 easy:單個(gè)函數(shù),無復(fù)雜依賴

單個(gè)函數(shù),并且外部依賴都是開源庫,那么直接把代碼貼上去就行。

這里我們用圖片壓縮的獨(dú)立函數(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里,完美運(yùn)行,說明簡單函數(shù)的單元測試生成還是很強(qiáng)的~

Part2 normal :里面有一些外部import

有外部定義引入的時(shí)候,最好把外部定義直接貼上(不涉及mock)。

這里舉例的函數(shù)出現(xiàn)了外部依賴 entity.ArchiveWithData等定義,所以最好要加上外部定義在輸入中:

寫下列代碼中ConvertArchiveWithDataToBotTextMsg函數(shù)的單測,要求覆蓋全部邏輯。
斷言使用require包。
測試用例以testCases的形式輸出。
// ConvertArchiveWithDataToBotTextMsg 將畫作稿件信息轉(zhuǎn)換為企微機(jī)器人文本消息
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
}
一些導(dǎo)入的定義如下:
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)過少量修改后可以直接運(yùn)行:(因?yàn)樯佥斎肓艘粋€(gè)結(jié)構(gòu)體的定義,導(dǎo)致它猜測了一些字段)

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)
      })
   }
}

Part3 hard:對(duì)外部repo進(jìn)行mock(gomock舉例)

外部依賴越多,prompt template的構(gòu)建可能就越復(fù)雜

ps.實(shí)際上gomonkey它也可以寫

外部依賴repo的部分,要求gpt使用gomock進(jìn)行對(duì)應(yīng)repo的mock即可。

外部定義最好進(jìn)行剪枝。

輸入:

寫下列代碼中g(shù)etSeTuDescSlice函數(shù)的單測,要求覆蓋全部邏輯。
斷言使用require包。
測試用例以testCases的形式輸出。
對(duì)于m.repoSeTu.GetArchiveInfoSlice的調(diào)用,使用gomock進(jìn)行mock處理,假設(shè)已經(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 請(qǐng)求結(jié)果
type QueryResult struct {
   Error        string    `json:"error"`
   ArchiveSlice []Archive `json:"data"`
   //picPaths     []string
}

輸出:

假設(shè)已經(jīng)有mock文件:
// mock_repoSetu 是通過mockgen自動(dòng)生成的,用于模擬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對(duì)象
            mockRepoSeTu := mock_repoSetu.NewMockRepoSeTu(ctrl)
            m := &CronTab{repoSeTu: mockRepoSeTu}
            // 設(shè)置mock對(duì)象返回值
            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)用成功的情況,
設(shè)置查詢條件R18=0,Tags="tag1", "tag2",PicSize="large",期望返回多條圖片信息;
測試用例"error"模擬了getSeTuDescSlice調(diào)用失敗的情況,
設(shè)置查詢條件R18=1,Tags="tag3",PicSize="small", "medium",期望返回錯(cuò)誤信息。

生成的代碼太長,幾點(diǎn)總結(jié):

  • gomock代碼可以正常生成,但是外部repo定義最好清晰明了,可以直接把interface定義貼上去,會(huì)被認(rèn)知到。
  • 粘貼到ide中,發(fā)現(xiàn)主要問題是包名導(dǎo)入問題,簡單修改即可

生成的代碼有一些報(bào)錯(cuò):

修改引入的類型名后即可:

修改后運(yùn)行成功:

如果需要生成后人工修改量更低的話,需要更精確的上下文信息(包名、函數(shù)定義、描述等)

一些痛點(diǎn)

  • ChatGPT單次對(duì)話有長度限制,所以一次盡量只進(jìn)行一個(gè)函數(shù)的單元測試生成。
  • 需要外部 repo mock的待測函數(shù),需要的上下文很多,并且引入的無關(guān)字段需要剪枝避免污染輸入
  • 一些復(fù)雜的私有包,看起來是比較不好支持的(嘗試過在prompt中教會(huì)他,但是效果不好)
  • 一些復(fù)雜邏輯和邊界情況,ai可能口是心非(寫的用例和表述不一致,比如經(jīng)典的20%*20%=400%,但是他知道這是錯(cuò)的)
  • 對(duì)于分層不是很清晰的項(xiàng)目,對(duì)函數(shù)/repo打樁可能很困難,這個(gè)時(shí)候要描述清楚就比較困難了,gomonkey可能比較適用

其他用法

  • 寫好測試方法,讓gpt幫助擴(kuò)充用例集
  • 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
}
您好!下面是一個(gè)優(yōu)化版的代碼,主要改進(jìn)在于:
減少對(duì)象的創(chuàng)建,避免額外的內(nèi)存分配和垃圾回收。
增加錯(cuò)誤檢查,避免可能的錯(cuò)誤被忽略。
以下是優(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
}
嘗試減少了對(duì)象的創(chuàng)建,使用了 bytes.Reader 代替了 bytes.NewBuffer 和 ioutil.ReadAll。
在處理圖片過程中,使用 bytes.Buffer 代替了 newBuf 這個(gè)額外的對(duì)象。
同時(shí)也將函數(shù)的返回值簡化,只返回處理后的 []byte 和可能的錯(cuò)誤,避免了空值的返回。最后,增加錯(cuò)誤檢查,保證代碼健壯性。

以上就是Golang使用ChatGPT生成單元測試實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Golang ChatGPT單元測試的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Lua讀寫文件代碼示例

    Lua讀寫文件代碼示例

    這篇文章主要介紹了Lua讀寫文件代碼示例,本文講解了讀寫文件的模式以及讀寫文件代碼實(shí)例,需要的朋友可以參考下
    2015-04-04
  • Lua腳本語言入門筆記

    Lua腳本語言入門筆記

    這篇文章主要介紹了Lua腳本語言入門筆記,本文講解了什么是Lua、Lua里的注釋、Lua里的變量、Lua里的運(yùn)算符、Lua里的數(shù)據(jù)類型、Lua里的代碼塊、Lua里的關(guān)系類型、Lua里的函數(shù)、Lua里的類等內(nèi)容,需要的朋友可以參考下
    2014-12-12
  • Lua中的弱引用介紹

    Lua中的弱引用介紹

    這篇文章主要介紹了Lua中的弱引用介紹,本文用一個(gè)實(shí)例講解了Lua弱引用的相關(guān)知識(shí),需要的朋友可以參考下
    2015-04-04
  • Lua中table里內(nèi)嵌table的例子

    Lua中table里內(nèi)嵌table的例子

    這篇文章主要介紹了Lua中table里內(nèi)嵌table的例子,本文同時(shí)講解了如何訪問內(nèi)嵌table方法的例子,需要的朋友可以參考下
    2015-04-04
  • Lua教程(九):元表與元方法詳解

    Lua教程(九):元表與元方法詳解

    這篇文章主要介紹了Lua教程(九):元表與元方法詳解,本文講解了算術(shù)類的元方法、關(guān)系類的元方法、庫定義的元方法、table訪問的元方法等內(nèi)容,需要的朋友可以參考下
    2015-04-04
  • 解析Lua中的全局環(huán)境、包、模塊組織結(jié)構(gòu)

    解析Lua中的全局環(huán)境、包、模塊組織結(jié)構(gòu)

    Lua中也擁有和Python世界相似的代碼的作用范圍和組織方式,下面我們就來簡單解析Lua中的全局環(huán)境、包、模塊組織結(jié)構(gòu),需要的朋友可以參考下
    2016-06-06
  • 簡單談?wù)刲ua和c的交互

    簡單談?wù)刲ua和c的交互

    要理解Lua和C++交互,首先要理解Lua堆棧。簡單來說,Lua和C/C++語言通信的主要方法是一個(gè)無處不在的虛擬棧。棧的特點(diǎn)是先進(jìn)后出。
    2016-01-01
  • Ruby元編程技術(shù)詳解(Ruby Metaprogramming techniques)

    Ruby元編程技術(shù)詳解(Ruby Metaprogramming techniques)

    這篇文章主要介紹了Ruby元編程技術(shù)詳解(Ruby Metaprogramming techniques),本文講解了11個(gè)關(guān)于元編程的技術(shù)和技巧,需要的朋友可以參考下
    2015-04-04
  • Lua中的閉合函數(shù)、非全局函數(shù)與函數(shù)的尾調(diào)用詳解

    Lua中的閉合函數(shù)、非全局函數(shù)與函數(shù)的尾調(diào)用詳解

    這篇文章主要介紹了Lua中的閉合函數(shù)、非全局函數(shù)與函數(shù)的尾調(diào)用詳解,本文對(duì)這2種函數(shù)和尾調(diào)用做了深入研究,需要的朋友可以參考下
    2014-09-09
  • Lua中函數(shù)的幾個(gè)特別之處探究

    Lua中函數(shù)的幾個(gè)特別之處探究

    這篇文章主要介紹了Lua中函數(shù)的幾個(gè)特別之處探究,本文講解了Lua的函數(shù)創(chuàng)建、函數(shù)的參數(shù)、函數(shù)參數(shù)個(gè)數(shù)自適應(yīng)、函數(shù)多重返回值等內(nèi)容,需要的朋友可以參考下
    2014-09-09

最新評(píng)論

桂东县| 武定县| 泸溪县| 英超| 富蕴县| 英山县| 泸水县| 阜南县| 抚顺市| 茶陵县| 精河县| 新巴尔虎右旗| 明溪县| 德州市| 嘉祥县| 吉林省| 白城市| 满城县| 盐源县| 莲花县| 阜南县| 哈尔滨市| 建昌县| 洛阳市| 邹城市| 横峰县| 化德县| 汉寿县| 双桥区| 陈巴尔虎旗| 腾冲县| 高邑县| 漯河市| 太白县| 杨浦区| 昌乐县| 惠水县| 扶风县| 阿图什市| 乐陵市| 托里县|