ToB企服应用市场:ToB评测及商务社交产业平台

标题: 用 GPT-4 给开源项目 GoPool 重构测试代码 - 每天5分钟玩转 GPT 编程系列(8 [打印本页]

作者: 种地    时间: 2023-8-29 05:52
标题: 用 GPT-4 给开源项目 GoPool 重构测试代码 - 每天5分钟玩转 GPT 编程系列(8
目录

1. 好险,差点被喷

早几天发了一篇文章:《仅三天,我用 GPT-4 生成了性能全网第一的 Golang Worker Pool,轻松打败 GitHub 万星项目》,这标题是挺容易被怼,哇咔咔;不过最终“下场”不算惨,怼的声音很少,相反很多开发者马上就在自己的项目里开始使用 GoPool了,刺激!
等等,你以为“刺激”的只是“我”?咳咳,其实,那些开发者也感觉很“刺激”。这不,他们被螃蟹壳塞牙了,你看 GoPool 的 issue 列表

容我辩解5秒钟)Bugs 有归有,不过平均一个 issue 的解决时间没有超过半天,至少我提供的售后服务还可以的,对吧?哈哈……
5秒到了)不得不承认:一个足够健壮的 Worker Pool 还是需要花点时间来打磨。
那就再打磨一下吧:

2. 重构测试代码

当前所有测试用例都放在 gopool_test.go 中,这个文件已经接近200行了,维护起来开始吃力。所以我决定今天先让 GPT-4 来改造一下测试用例的组织方式。
下文使用 DevChat 和 GPT-4 交互。如果你对 DevChat 感兴趣,可以跳转阅读这篇文章:
2.1 引入 Ginkgo 测试框架

当前我的测试代码全部在 gopool_test.go 中,我觉得可读性不够好。我希望你引入 Ginkgo 测试框架来帮我重新组织测试代码。首先我只需要你告诉我如何引入 Ginkgo,搭建测试骨架代码,而不用编写具体的测试用例。
DevChat 的回答如下:
要引入 Ginkgo 测试框架并搭建测试骨架代码,你需要按照以下步骤进行:
  1. go get github.com/onsi/ginkgo/ginkgo
  2. go get github.com/onsi/gomega/...
复制代码
  1. ginkgo bootstrap
复制代码
这将会生成一个名为 suite_test.go 的文件,其中包含了一个基本的测试套件骨架。
  1. ginkgo generate gopool
复制代码
这将会生成一个名为 gopool_test.go 的文件,其中包含了一个基本的测试文件骨架。
  1. var _ = Describe("Gopool", func() {
  2.     Context("when something happens", func() {
  3.         It("should do something", func() {
  4.             // Your test code goes here
  5.         })
  6.     })
  7. })
复制代码
在这个骨架中,Describe 用于描述你正在测试的对象,Context 用于描述你正在测试的场景,It 用于描述你的期望结果。
以上就是引入 Ginkgo 并搭建测试骨架代码的步骤。你可以根据你的需求修改和添加测试用例。
因为 gopool_test.go 文件已经存在,Ginkgo 也没有智能到能够直接“merge”,所以我先将当前代码备份到了 gopool_1_test.go 中:

2.2 尝试改造旧的测试用例

骨架有了,接下来我准备尝试让 GPT-4 来重写代码。
我将原先的测试用例到备份到了 gopool_1_test.go 中,现在我需要你用 Ginkgo 的最佳实践重构测试代码,新的代码将会放到 gopool_test.go 中。
DevChat 的回答如下:
首先,我们需要将原先的测试用例转换为 Ginkgo 的格式。这包括将每个测试用例转换为一个 It 块,并使用 Gomega 断言库来替换原先的断言。以下是转换后的测试代码:
  1. package gopool_test
  2. import (
  3.         . "github.com/onsi/ginkgo"
  4.         . "github.com/onsi/gomega"
  5.         "github.com/devchat-ai/gopool"
  6. )
  7. var _ = Describe("Gopool", func() {
  8.         Describe("With Mutex", func() {
  9.                 It("should work correctly", func() {
  10.                         // Your test code goes here
  11.                 })
  12.         })
  13.         Describe("With SpinLock", func() {
  14.                 It("should work correctly", func() {
  15.                         // Your test code goes here
  16.                 })
  17.         })
  18.         Describe("With Error", func() {
  19.                 It("should work correctly", func() {
  20.                         // Your test code goes here
  21.                 })
  22.         })
  23.         Describe("With Result", func() {
  24.                 It("should work correctly", func() {
  25.                         // Your test code goes here
  26.                 })
  27.         })
  28.         Describe("With Retry", func() {
  29.                 It("should work correctly", func() {
  30.                         // Your test code goes here
  31.                 })
  32.         })
  33.         Describe("With Timeout", func() {
  34.                 It("should work correctly", func() {
  35.                         // Your test code goes here
  36.                 })
  37.         })
  38.         Describe("With MinWorkers", func() {
  39.                 It("should work correctly", func() {
  40.                         // Your test code goes here
  41.                 })
  42.         })
  43. })
复制代码
这只是一个骨架代码,你需要将原先的测试代码复制到对应的 It 块中,并使用 Gomega 断言库来替换原先的断言。例如,你可以将 t.Errorf 替换为 Expect().To(Equal())。
对于基准测试,Ginkgo 并不直接支持,但你可以使用 Go 的原生 testing 包来进行基准测试。你可以将基准测试代码放在一个单独的文件中,例如 gopool_benchmark_test.go。
啥?我感觉 GPT-4 偷懒了,居然不直接给我把代码写好。另外我没记错的话,Ginkgo 是支持“基准测试”的,可能是 GPT-4 的训练数据太旧导致它不知道这件事。(没错,这时候是它不是她。)

于是我先“笑纳”了这段代码,然后拆解问题,暂且将压测代码放到单独的文件,让 GPT-4 安心把功能测试代码重构好,然后我再和它算“基准测试”的账。
此刻我就有了一个新文件:
里面内容如下:
  1. package gopool
  2. import (
  3.         "sync"
  4.         "testing"
  5.         "time"
  6.         "github.com/daniel-hutao/spinlock"
  7. )
  8. func BenchmarkGoPoolWithMutex(b *testing.B) {
  9.         var wg sync.WaitGroup
  10.         var taskNum = int(1e6)
  11.         pool := NewGoPool(1e4, WithLock(new(sync.Mutex)))
  12.         defer pool.Release()
  13.         b.ResetTimer()
  14.         for i := 0; i < b.N; i++ {
  15.                 wg.Add(taskNum)
  16.                 for num := 0; num < taskNum; num++ {
  17.                         pool.AddTask(func() (interface{}, error) {
  18.                                 time.Sleep(10 * time.Millisecond)
  19.                                 wg.Done()
  20.                                 return nil, nil
  21.                         })
  22.                 }
  23.                 wg.Wait()
  24.         }
  25.         b.StopTimer()
  26. }
  27. func BenchmarkGoPoolWithSpinLock(b *testing.B) {
  28.         var wg sync.WaitGroup
  29.         var taskNum = int(1e6)
  30.         pool := NewGoPool(1e4, WithLock(new(spinlock.SpinLock)))
  31.         defer pool.Release()
  32.         b.ResetTimer()
  33.         for i := 0; i < b.N; i++ {
  34.                 wg.Add(taskNum)
  35.                 for num := 0; num < taskNum; num++ {
  36.                         pool.AddTask(func() (interface{}, error) {
  37.                                 time.Sleep(10 * time.Millisecond)
  38.                                 wg.Done()
  39.                                 return nil, nil
  40.                         })
  41.                 }
  42.                 wg.Wait()
  43.         }
  44.         b.StopTimer()
  45. }
  46. func BenchmarkGoroutines(b *testing.B) {
  47.         var wg sync.WaitGroup
  48.         var taskNum = int(1e6)
  49.         for i := 0; i < b.N; i++ {
  50.                 wg.Add(taskNum)
  51.                 for num := 0; num < taskNum; num++ {
  52.                         go func() (interface{}, error) {
  53.                                 time.Sleep(10 * time.Millisecond)
  54.                                 wg.Done()
  55.                                 return nil, nil
  56.                         }()
  57.                 }
  58.                 wg.Wait()
  59.         }
  60. }
复制代码
而 gopool_1_test.go 的内容我就不赘述了,就是原先的全量测试代码减去上面这些基准测试代码。那么接下来问题就拆分成了2个:
不过目测本文篇幅有点长了,所以我决定今天先完成功能测试代码的改造,基准测试下次再说。
2.3 重构功能测试代码

接下来该让 GPT-4 开始重构功能测试代码了。
帮我将 gopool_1_test.go 中的用 testing 库编写的测试代码全部用 Ginkgo 的方式重构,更新到 gopool_test.go 中。

提示: DevChat 给出的代码库右上角有一个“diff”按钮,点一下可以进入“diff 视图”,这样就能看到当前打开文件中的代码和 GPT-4 给出的代码之间的 diff,然后你就能清晰地看到 GPT-4 给出的代码具体修改了什么了。
DevChat 的完整回答是:
好的,我将会按照你的要求,将 gopool_1_test.go 中的测试代码用 Ginkgo 的方式重构,并更新到 gopool_test.go 中。
[code]package gopool_testimport (        "errors"        "sync"        "sync/atomic"        "time"        "github.com/daniel-hutao/spinlock"        "github.com/devchat-ai/gopool"        . "github.com/onsi/ginkgo"        . "github.com/onsi/gomega")var _ = Describe("Gopool", func() {        Describe("With Mutex", func() {                It("should work correctly", func() {                        pool := gopool.NewGoPool(100, gopool.WithLock(new(sync.Mutex)))                        defer pool.Release()                        for i := 0; i < 1000; i++ {                                pool.AddTask(func() (interface{}, error) {                                        time.Sleep(10 * time.Millisecond)                                        return nil, nil                                })                        }                        pool.Wait()                })        })        Describe("With SpinLock", func() {                It("should work correctly", func() {                        pool := gopool.NewGoPool(100, gopool.WithLock(new(spinlock.SpinLock)))                        defer pool.Release()                        for i := 0; i < 1000; i++ {                                pool.AddTask(func() (interface{}, error) {                                        time.Sleep(10 * time.Millisecond)                                        return nil, nil                                })                        }                        pool.Wait()                })        })        Describe("With Error", func() {                It("should work correctly", func() {                        var errTaskError = errors.New("task error")                        pool := gopool.NewGoPool(100, gopool.WithErrorCallback(func(err error) {                                Expect(err).To(Equal(errTaskError))                        }))                        defer pool.Release()                        for i := 0; i < 1000; i++ {                                pool.AddTask(func() (interface{}, error) {                                        return nil, errTaskError                                })                        }                        pool.Wait()                })        })        Describe("With Result", func() {                It("should work correctly", func() {                        var expectedResult = "task result"                        pool := gopool.NewGoPool(100, gopool.WithResultCallback(func(result interface{}) {                                Expect(result).To(Equal(expectedResult))                        }))                        defer pool.Release()                        for i := 0; i < 1000; i++ {                                pool.AddTask(func() (interface{}, error) {                                        return expectedResult, nil                                })                        }                        pool.Wait()                })        })        Describe("With Retry", func() {                It("should work correctly", func() {                        var retryCount = int32(3)                        var taskError = errors.New("task error")                        var taskRunCount int32 = 0                        pool := gopool.NewGoPool(100, gopool.WithRetryCount(int(retryCount)))                        defer pool.Release()                        pool.AddTask(func() (interface{}, error) {                                atomic.AddInt32(&taskRunCount, 1)                                if taskRunCount




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4