经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 大数据/云/AI » 人工智能基础 » 查看文章
用 GPT-4 给开源项目 GoPool 重构测试代码 - 每天5分钟玩转 GPT 编程系列(8)
来源:cnblogs  作者:胡说云原生  时间:2023/8/18 9:29:17  对本文有异议

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 测试框架

  • 我对 DevChat 说:

当前我的测试代码全部在 gopool_test.go 中,我觉得可读性不够好。我希望你引入 Ginkgo 测试框架来帮我重新组织测试代码。首先我只需要你告诉我如何引入 Ginkgo,搭建测试骨架代码,而不用编写具体的测试用例。

DevChat 的回答如下:


要引入 Ginkgo 测试框架并搭建测试骨架代码,你需要按照以下步骤进行:

  1. 首先,你需要安装 Ginkgo 和 Gomega。Ginkgo 是一个 Go 测试框架,Gomega 是一个配套的断言库。你可以通过以下命令安装它们:
  1. go get github.com/onsi/ginkgo/ginkgo
  2. go get github.com/onsi/gomega/...
  1. 然后,你需要在你的测试目录下生成一个新的 Ginkgo 测试套件。你可以通过以下命令生成:
  1. ginkgo bootstrap

这将会生成一个名为 suite_test.go 的文件,其中包含了一个基本的测试套件骨架。

  1. 接下来,你可以为你的 gopool 包生成一个 Ginkgo 测试文件。你可以通过以下命令生成:
  1. ginkgo generate gopool

这将会生成一个名为 gopool_test.go 的文件,其中包含了一个基本的测试文件骨架。

  1. 最后,你可以在 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 来重写代码。

  • 我对 DevChat 说:

我将原先的测试用例到备份到了 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 安心把功能测试代码重构好,然后我再和它算“基准测试”的账。

此刻我就有了一个新文件:

  • gopool_benchmark_test.go

里面内容如下:

  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个:

  1. 用 Ginkgo 的方式改造功能测试代码
  2. 用 Ginkgo 的方式改造基准测试代码

不过目测本文篇幅有点长了,所以我决定今天先完成功能测试代码的改造,基准测试下次再说。

2.3 重构功能测试代码

接下来该让 GPT-4 开始重构功能测试代码了。

  • 我对 DevChat 说:

帮我将 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 中。

  1. package gopool_test
  2. import (
  3. "errors"
  4. "sync"
  5. "sync/atomic"
  6. "time"
  7. "github.com/daniel-hutao/spinlock"
  8. "github.com/devchat-ai/gopool"
  9. . "github.com/onsi/ginkgo"
  10. . "github.com/onsi/gomega"
  11. )
  12. var _ = Describe("Gopool", func() {
  13. Describe("With Mutex", func() {
  14. It("should work correctly", func() {
  15. pool := gopool.NewGoPool(100, gopool.WithLock(new(sync.Mutex)))
  16. defer pool.Release()
  17. for i := 0; i < 1000; i++ {
  18. pool.AddTask(func() (interface{}, error) {
  19. time.Sleep(10 * time.Millisecond)
  20. return nil, nil
  21. })
  22. }
  23. pool.Wait()
  24. })
  25. })
  26. Describe("With SpinLock", func() {
  27. It("should work correctly", func() {
  28. pool := gopool.NewGoPool(100, gopool.WithLock(new(spinlock.SpinLock)))
  29. defer pool.Release()
  30. for i := 0; i < 1000; i++ {
  31. pool.AddTask(func() (interface{}, error) {
  32. time.Sleep(10 * time.Millisecond)
  33. return nil, nil
  34. })
  35. }
  36. pool.Wait()
  37. })
  38. })
  39. Describe("With Error", func() {
  40. It("should work correctly", func() {
  41. var errTaskError = errors.New("task error")
  42. pool := gopool.NewGoPool(100, gopool.WithErrorCallback(func(err error) {
  43. Expect(err).To(Equal(errTaskError))
  44. }))
  45. defer pool.Release()
  46. for i := 0; i < 1000; i++ {
  47. pool.AddTask(func() (interface{}, error) {
  48. return nil, errTaskError
  49. })
  50. }
  51. pool.Wait()
  52. })
  53. })
  54. Describe("With Result", func() {
  55. It("should work correctly", func() {
  56. var expectedResult = "task result"
  57. pool := gopool.NewGoPool(100, gopool.WithResultCallback(func(result interface{}) {
  58. Expect(result).To(Equal(expectedResult))
  59. }))
  60. defer pool.Release()
  61. for i := 0; i < 1000; i++ {
  62. pool.AddTask(func() (interface{}, error) {
  63. return expectedResult, nil
  64. })
  65. }
  66. pool.Wait()
  67. })
  68. })
  69. Describe("With Retry", func() {
  70. It("should work correctly", func() {
  71. var retryCount = int32(3)
  72. var taskError = errors.New("task error")
  73. var taskRunCount int32 = 0
  74. pool := gopool.NewGoPool(100, gopool.WithRetryCount(int(retryCount)))
  75. defer pool.Release()
  76. pool.AddTask(func() (interface{}, error) {
  77. atomic.AddInt32(&taskRunCount, 1)
  78. if taskRunCount <= retryCount {
  79. return nil, taskError
  80. }
  81. return nil, nil
  82. })
  83. pool.Wait()
  84. Expect(atomic.LoadInt32(&taskRunCount)).To(Equal(retryCount + 1))
  85. })
  86. })
  87. Describe("With Timeout", func() {
  88. It("should work correctly", func() {
  89. var taskRun int32
  90. pool := gopool.NewGoPool(100, gopool.WithTimeout(100*time.Millisecond), gopool.WithErrorCallback(func(err error) {
  91. Expect(err.Error()).To(Equal("task timed out"))
  92. atomic.StoreInt32(&taskRun, 1)
  93. }))
  94. defer pool.Release()
  95. pool.AddTask(func() (interface{}, error) {
  96. time.Sleep(200 * time.Millisecond)
  97. return nil, nil
  98. })
  99. pool.Wait()
  100. Expect(atomic.LoadInt32(&taskRun)).To(Equal(int32(1)))
  101. })
  102. })
  103. Describe("With MinWorkers", func() {
  104. It("should work correctly", func() {
  105. var minWorkers = 50
  106. pool := gopool.NewGoPool(100, gopool.WithMinWorkers(minWorkers))
  107. defer pool.Release()
  108. Expect(pool.GetWorkerCount()).To(Equal(minWorkers))
  109. })
  110. })
  111. })

这段代码将 gopool_1_test.go 中的测试用例转换为 Ginkgo 的格式,并使用 Gomega 断言库来替换原先的断言。


这段代码基本是 ok 的,不过运行的时候会遇到这个错误:

  1. go test ./...
  2. /var/folders/gv/f94q_vyx2m17d_36sf0zkm0r0000gn/T/go-build3608449998/b001/gopool.test flag redefined: ginkgo.seed
  3. panic: /var/folders/gv/f94q_vyx2m17d_36sf0zkm0r0000gn/T/go-build3608449998/b001/gopool.test flag redefined: ginkgo.seed
  4. goroutine 1 [running]:
  5. flag.(*FlagSet).Var(0x14000114120, {0x1049bd108, 0x104c0d4a0}, {0x1400011af70, 0xb}, {0x10486d229, 0x2a})
  6. /usr/local/go/src/flag/flag.go:982 +0x2a4
  7. flag.(*FlagSet).Int64Var(...)
  8. /usr/local/go/src/flag/flag.go:769
  9. github.com/onsi/ginkgo/config.Flags(0x14000191ba8?, {0x10485657a?, 0x989680?}, 0x1)
  10. /Users/danielhu/go/pkg/mod/github.com/onsi/ginkgo@v1.16.5/config/config.go:75 +0xe0
  11. github.com/onsi/ginkgo.init.0()
  12. /Users/danielhu/go/pkg/mod/github.com/onsi/ginkgo@v1.16.5/ginkgo_dsl.go:53 +0x38
  13. FAIL github.com/devchat-ai/gopool 3.889s
  14. FAIL

这时候 Google 一下,可以找到 Ginkgo issue 875。改起来也很容易,在 gopool_test.go 的开头应该将 . "github.com/onsi/ginkgo" 改成 . "github.com/onsi/ginkgo/v2"

所以目前 go.mod 就变成了这样:

  1. module github.com/devchat-ai/gopool
  2. go 1.20
  3. require (
  4. github.com/daniel-hutao/spinlock v0.1.0
  5. github.com/onsi/ginkgo/v2 v2.11.0
  6. github.com/onsi/gomega v1.27.10
  7. )
  8. require (
  9. github.com/go-logr/logr v1.2.4 // indirect
  10. github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
  11. github.com/google/go-cmp v0.5.9 // indirect
  12. github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
  13. golang.org/x/net v0.12.0 // indirect
  14. golang.org/x/sys v0.10.0 // indirect
  15. golang.org/x/text v0.11.0 // indirect
  16. golang.org/x/tools v0.9.3 // indirect
  17. gopkg.in/yaml.v3 v3.0.1 // indirect
  18. )

再装一个 VS Code 的 ginkgo 插件,圆满了:

通过 ginkgo 插件运行一下:

也可以在命令行里自己敲 ginkgo -v 感受下更详细的结果输出:

  1. $ ginkgo -v
  2. Running Suite: Gopool Suite - /Users/danielhu/go/mycode/gopool
  3. ==============================================================
  4. Random Seed: 1692081153
  5. Will run 7 of 7 specs
  6. ------------------------------
  7. Gopool With Mutex should work correctly
  8. /Users/danielhu/go/mycode/gopool/gopool_test.go:17
  9. ? [0.203 seconds]
  10. ------------------------------
  11. Gopool With SpinLock should work correctly
  12. /Users/danielhu/go/mycode/gopool/gopool_test.go:31
  13. ? [0.201 seconds]
  14. ------------------------------
  15. Gopool With Error should work correctly
  16. /Users/danielhu/go/mycode/gopool/gopool_test.go:45
  17. ? [0.102 seconds]
  18. ------------------------------
  19. Gopool With Result should work correctly
  20. /Users/danielhu/go/mycode/gopool/gopool_test.go:62
  21. ? [0.102 seconds]
  22. ------------------------------
  23. Gopool With Retry should work correctly
  24. /Users/danielhu/go/mycode/gopool/gopool_test.go:79
  25. ? [0.101 seconds]
  26. ------------------------------
  27. Gopool With Timeout should work correctly
  28. /Users/danielhu/go/mycode/gopool/gopool_test.go:102
  29. ? [0.202 seconds]
  30. ------------------------------
  31. Gopool With MinWorkers should work correctly
  32. /Users/danielhu/go/mycode/gopool/gopool_test.go:123
  33. ? [0.001 seconds]
  34. ------------------------------
  35. Ran 7 of 7 Specs in 0.916 seconds
  36. SUCCESS! -- 7 Passed | 0 Failed | 0 Pending | 0 Skipped
  37. PASS
  38. Ginkgo ran 1 suite in 4.759009833s
  39. Test Suite Passed

到现在,测试相关的文件就有了3个:

  • gopool_benchmark_test.go
  • gopool_suite_test.go
  • gopool_test.go

到此,提个 PR :Refactor tests using Ginkgo and Gomega

3. 总结

因为 GPT-4 的训练数据是大约2年前的,也就是最近2年这个世界发生了啥它是不知道的。所以对于一些变化大的库,对于一些版本敏感的问题,你要对 GPT 给出的代码保持警惕。有时候你需要的代码对版本不敏感,那无所谓;反之,及时 Google 一下。

总之,擅用 GPT,但别只用 GPT。偶尔还是想想你的老相好“Google Bing 和度娘”。

原文链接:https://www.cnblogs.com/daniel-hutao/p/devchat-gpt-in-5min-8-ut.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号