用 GPT-4 给开源项目 GoPool 重构测试代码 - 每天5分钟玩转 GPT 编程系列(8)
1. 好险,差点被喷
早几天发了一篇文章:《仅三天,我用 GPT-4 生成了性能全网第一的 Golang Worker Pool,轻松打败 GitHub 万星项目》,这标题是挺容易被怼,哇咔咔;不过最终“下场”不算惨,怼的声音很少,相反很多开发者马上就在自己的项目里开始使用 GoPool了,刺激!
等等,你以为“刺激”的只是“我”?咳咳,其实,那些开发者也感觉很“刺激”。这不,他们被螃蟹壳塞牙了,你看 GoPool 的 issue 列表:
(容我辩解5秒钟)Bugs 有归有,不过平均一个 issue 的解决时间没有超过半天,至少我提供的售后服务还可以的,对吧?哈哈……
(5秒到了)不得不承认:一个足够健壮的 Worker Pool 还是需要花点时间来打磨。
那就再打磨一下吧:
- 解 bugs
- 完善测试用例
2. 重构测试代码
当前所有测试用例都放在 gopool_test.go
中,这个文件已经接近200行了,维护起来开始吃力。所以我决定今天先让 GPT-4 来改造一下测试用例的组织方式。
下文使用 DevChat 和 GPT-4 交互。如果你对 DevChat 感兴趣,可以跳转阅读这篇文章:
- 《DevChat:将 GPT-4 无缝融入 VS Code,极致提升你的编程体验》
- 如果你想要注册 DevChat,点击这个链接可以免费注册,目前 DevChat 还在公测中,不收费。
2.1 引入 Ginkgo 测试框架
- 我对 DevChat 说:
当前我的测试代码全部在 gopool_test.go 中,我觉得可读性不够好。我希望你引入 Ginkgo 测试框架来帮我重新组织测试代码。首先我只需要你告诉我如何引入 Ginkgo,搭建测试骨架代码,而不用编写具体的测试用例。
DevChat 的回答如下:
要引入 Ginkgo 测试框架并搭建测试骨架代码,你需要按照以下步骤进行:
- 首先,你需要安装 Ginkgo 和 Gomega。Ginkgo 是一个 Go 测试框架,Gomega 是一个配套的断言库。你可以通过以下命令安装它们:
go get github.com/onsi/ginkgo/ginkgo
go get github.com/onsi/gomega/...
- 然后,你需要在你的测试目录下生成一个新的 Ginkgo 测试套件。你可以通过以下命令生成:
ginkgo bootstrap
这将会生成一个名为 suite_test.go
的文件,其中包含了一个基本的测试套件骨架。
- 接下来,你可以为你的
gopool
包生成一个 Ginkgo 测试文件。你可以通过以下命令生成:
ginkgo generate gopool
这将会生成一个名为 gopool_test.go
的文件,其中包含了一个基本的测试文件骨架。
- 最后,你可以在
gopool_test.go
文件中添加你的测试用例。以下是一个基本的测试用例骨架:
var _ = Describe("Gopool", func() {
Context("when something happens", func() {
It("should do something", func() {
// Your test code goes here
})
})
})
在这个骨架中,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 断言库来替换原先的断言。以下是转换后的测试代码:
package gopool_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/devchat-ai/gopool"
)
var _ = Describe("Gopool", func() {
Describe("With Mutex", func() {
It("should work correctly", func() {
// Your test code goes here
})
})
Describe("With SpinLock", func() {
It("should work correctly", func() {
// Your test code goes here
})
})
Describe("With Error", func() {
It("should work correctly", func() {
// Your test code goes here
})
})
Describe("With Result", func() {
It("should work correctly", func() {
// Your test code goes here
})
})
Describe("With Retry", func() {
It("should work correctly", func() {
// Your test code goes here
})
})
Describe("With Timeout", func() {
It("should work correctly", func() {
// Your test code goes here
})
})
Describe("With MinWorkers", func() {
It("should work correctly", func() {
// Your test code goes here
})
})
})
这只是一个骨架代码,你需要将原先的测试代码复制到对应的 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
里面内容如下:
package gopool
import (
"sync"
"testing"
"time"
"github.com/daniel-hutao/spinlock"
)
func BenchmarkGoPoolWithMutex(b *testing.B) {
var wg sync.WaitGroup
var taskNum = int(1e6)
pool := NewGoPool(1e4, WithLock(new(sync.Mutex)))
defer pool.Release()
b.ResetTimer()
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
pool.AddTask(func() (interface{}, error) {
time.Sleep(10 * time.Millisecond)
wg.Done()
return nil, nil
})
}
wg.Wait()
}
b.StopTimer()
}
func BenchmarkGoPoolWithSpinLock(b *testing.B) {
var wg sync.WaitGroup
var taskNum = int(1e6)
pool := NewGoPool(1e4, WithLock(new(spinlock.SpinLock)))
defer pool.Release()
b.ResetTimer()
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
pool.AddTask(func() (interface{}, error) {
time.Sleep(10 * time.Millisecond)
wg.Done()
return nil, nil
})
}
wg.Wait()
}
b.StopTimer()
}
func BenchmarkGoroutines(b *testing.B) {
var wg sync.WaitGroup
var taskNum = int(1e6)
for i := 0; i < b.N; i++ {
wg.Add(taskNum)
for num := 0; num < taskNum; num++ {
go func() (interface{}, error) {
time.Sleep(10 * time.Millisecond)
wg.Done()
return nil, nil
}()
}
wg.Wait()
}
}
而 gopool_1_test.go
的内容我就不赘述了,就是原先的全量测试代码减去上面这些基准测试代码。那么接下来问题就拆分成了2个:
- 用 Ginkgo 的方式改造功能测试代码
- 用 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
中。
package gopool_test
import (
"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 <= retryCount {
return nil, taskError
}
return nil, nil
})
pool.Wait()
Expect(atomic.LoadInt32(&taskRunCount)).To(Equal(retryCount + 1))
})
})
Describe("With Timeout", func() {
It("should work correctly", func() {
var taskRun int32
pool := gopool.NewGoPool(100, gopool.WithTimeout(100*time.Millisecond), gopool.WithErrorCallback(func(err error) {
Expect(err.Error()).To(Equal("task timed out"))
atomic.StoreInt32(&taskRun, 1)
}))
defer pool.Release()
pool.AddTask(func() (interface{}, error) {
time.Sleep(200 * time.Millisecond)
return nil, nil
})
pool.Wait()
Expect(atomic.LoadInt32(&taskRun)).To(Equal(int32(1)))
})
})
Describe("With MinWorkers", func() {
It("should work correctly", func() {
var minWorkers = 50
pool := gopool.NewGoPool(100, gopool.WithMinWorkers(minWorkers))
defer pool.Release()
Expect(pool.GetWorkerCount()).To(Equal(minWorkers))
})
})
})
这段代码将 gopool_1_test.go
中的测试用例转换为 Ginkgo 的格式,并使用 Gomega 断言库来替换原先的断言。
这段代码基本是 ok 的,不过运行的时候会遇到这个错误:
go test ./...
/var/folders/gv/f94q_vyx2m17d_36sf0zkm0r0000gn/T/go-build3608449998/b001/gopool.test flag redefined: ginkgo.seed
panic: /var/folders/gv/f94q_vyx2m17d_36sf0zkm0r0000gn/T/go-build3608449998/b001/gopool.test flag redefined: ginkgo.seed
goroutine 1 [running]:
flag.(*FlagSet).Var(0x14000114120, {0x1049bd108, 0x104c0d4a0}, {0x1400011af70, 0xb}, {0x10486d229, 0x2a})
/usr/local/go/src/flag/flag.go:982 +0x2a4
flag.(*FlagSet).Int64Var(...)
/usr/local/go/src/flag/flag.go:769
github.com/onsi/ginkgo/config.Flags(0x14000191ba8?, {0x10485657a?, 0x989680?}, 0x1)
/Users/danielhu/go/pkg/mod/github.com/onsi/ginkgo@v1.16.5/config/config.go:75 +0xe0
github.com/onsi/ginkgo.init.0()
/Users/danielhu/go/pkg/mod/github.com/onsi/ginkgo@v1.16.5/ginkgo_dsl.go:53 +0x38
FAIL github.com/devchat-ai/gopool 3.889s
FAIL
这时候 Google 一下,可以找到 Ginkgo issue 875。改起来也很容易,在 gopool_test.go
的开头应该将 . "github.com/onsi/ginkgo"
改成 . "github.com/onsi/ginkgo/v2"
所以目前 go.mod
就变成了这样:
module github.com/devchat-ai/gopool
go 1.20
require (
github.com/daniel-hutao/spinlock v0.1.0
github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/gomega v1.27.10
)
require (
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.9.3 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
再装一个 VS Code 的 ginkgo 插件,圆满了:
通过 ginkgo 插件运行一下:
也可以在命令行里自己敲 ginkgo -v
感受下更详细的结果输出:
$ ginkgo -v
Running Suite: Gopool Suite - /Users/danielhu/go/mycode/gopool
==============================================================
Random Seed: 1692081153
Will run 7 of 7 specs
------------------------------
Gopool With Mutex should work correctly
/Users/danielhu/go/mycode/gopool/gopool_test.go:17
• [0.203 seconds]
------------------------------
Gopool With SpinLock should work correctly
/Users/danielhu/go/mycode/gopool/gopool_test.go:31
• [0.201 seconds]
------------------------------
Gopool With Error should work correctly
/Users/danielhu/go/mycode/gopool/gopool_test.go:45
• [0.102 seconds]
------------------------------
Gopool With Result should work correctly
/Users/danielhu/go/mycode/gopool/gopool_test.go:62
• [0.102 seconds]
------------------------------
Gopool With Retry should work correctly
/Users/danielhu/go/mycode/gopool/gopool_test.go:79
• [0.101 seconds]
------------------------------
Gopool With Timeout should work correctly
/Users/danielhu/go/mycode/gopool/gopool_test.go:102
• [0.202 seconds]
------------------------------
Gopool With MinWorkers should work correctly
/Users/danielhu/go/mycode/gopool/gopool_test.go:123
• [0.001 seconds]
------------------------------
Ran 7 of 7 Specs in 0.916 seconds
SUCCESS! -- 7 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS
Ginkgo ran 1 suite in 4.759009833s
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 和度娘”。
用 GPT-4 给开源项目 GoPool 重构测试代码 - 每天5分钟玩转 GPT 编程系列(8)的更多相关文章
- POI开源项目-PPT2PNG转换测试
第一次学使用软件去读取Office类文件. 今天百度了一下,发现Apache也是有这样的开源项目的,POI提供下列支持: 测试了一下HSLF对PPT的读操作,并将PPT转化为图片保存到指定目录: 使用 ...
- 给各位聚聚和大大介绍一个开源项目 Expression2Sql(转)
阅读目录 一.Expression2Sql介绍 二.单表简单查询 三.Where条件 四.多表关联查询 五.group by 六.order by 七.函数 八.delete 删除 九.update ...
- 使用github参与开源项目
github上有很多非常优秀的开源项目,作为开源项目的受益者,你否想过为开源项目贡献自己的代码?
- GitHub上史上最全的Android开源项目分类汇总 (转)
GitHub上史上最全的Android开源项目分类汇总 标签: github android 开源 | 发表时间:2014-11-23 23:00 | 作者:u013149325 分享到: 出处:ht ...
- GitHub上史上最全的Android开源项目分类汇总
今天在看博客的时候,无意中发现了 @Trinea 在GitHub上的一个项目 Android开源项目分类汇总 ,由于类容太多了,我没有一个个完整地看完,但是里面介绍的开源项目都非常有参考价值,包括很炫 ...
- C#.NET开源项目、机器学习、商务智能
所以原谅我,不能把所有的都发上来,太杂了,反而不好. 1..NET时间周期处理组件 这个组件很小,主要是对时间日期,特别是处理时间间隔以及时间范围非常方便.虽然.NET自带了时间日期的部分功能,但可能 ...
- 2014年国人开发的最热门的.NET开源项目 TOP 25
原文地址:http://www.cnphp6.com/archives/72213 1 奎宇工作室 / DotNetCodes C# 一些常用的功能性代码,可以减少许多开发时间,而且类与类之间没有什么 ...
- .NET开源项目 TOP 25
.NET开源项目 TOP 25 如果知道.NET项目在开源中国的git上所占的比重只有5%的话,为什么这个<2014年国人开发的最热门的开源软件TOP 100>榜中.NET项目那么少就是情 ...
- 2014年国内最热门的.NET开源项目TOP25
编者按:在2014年初时,微软宣布成立.NET基金会,全面支持开源项目.如今将过一年的时间,目前国内的开源项目到底如何了?下面我们就来细数一下国内25款比较优秀的.NET开源项目. 作者:acdoma ...
- Android 上传开源项目到 jcenter 实战踩坑之路
本文微信公众号「AndroidTraveler」首发. 背景 其实 Android 上传开源项目到 jcenter 并不是一件新鲜事,网上也有很多文章. 包括我本人在将开源项目上传到 jcenter ...
随机推荐
- 2022-10-03:给定一个正数n,比如6 表示数轴上有 0,1,2,3,4,5,6 <0 或者 >6 的位置认为无法到达 给定两个数字x和y,0<= x,y <= n 表示小人一开始在x的位置,它
2022-10-03:给定一个正数n,比如6 表示数轴上有 0,1,2,3,4,5,6 <0 或者 >6 的位置认为无法到达 给定两个数字x和y,0<= x,y <= n 表示 ...
- Netty自定义协议要素
魔数:用来判断是否是无效数据包 协议版本号:可以支持协议的升级 序列化算法:消息正文使用哪种序列化方式,可以扩展.例如:protobuf,json,hessian等 指令类型:跟业务相关,例如:登录, ...
- vue全家桶进阶之路29:Element Plus
Element Plus是一个用于Vue.js的UI组件库,为开发人员提供了一组可重用和可定制化的组件,用于构建现代Web应用程序.它是流行的Element UI库的扩展,重点是提高性能和可访问性. ...
- Python中json.dump()和json.dumps()的区别
一.图解 json.dumps(dict, indent):将Python对象转换成json字符串 json.dump(dict, file_pointer):将Python对象写入json文件 二. ...
- [SWPUCTF 2021 新生赛]PseudoProtocols
[SWPUCTF 2021 新生赛]PseudoProtocols 一.题目 二.WP 1.打开题目,发现提示我们是否能找到hint.php,并且发现URL有参数wllm.所以我们尝试利用PHP伪协议 ...
- Prompt Engineering优化原则 - 以Webshell代码解释为例
一.LLM prompt优化原则 本文围绕"PHP代码解释"这一任务,讨论LLM prompt优化原则. 代码样例如下: <?php echo "a5a5aa555 ...
- Python生成随机验证
Python生成随机验证码 Python生成随机验证码,需要使用PIL模块. 安装: 1 pip3 install pillow 基本使用 1. 创建图片 1 2 3 4 5 6 7 8 9 fr ...
- “古老”编程语言的最新选择!华为云发布CodeArts IDE for C/C++
摘要:华为云CodeArts IDE for C/C++正式上线,欢迎体验. 本文分享自华为云社区<"古老"编程语言的最新选择!华为云发布CodeArts IDE for C ...
- SpringBoot进阶教程(七十六)多维度排序查询
在项目中经常能遇到,需要对某些数据集合进行多维度排序的需求.对于集合多条件排序解决方案也有很多,今天我们就介绍一种,思路大致是设置一个分值的集合,这个分值是按照需求来设定大小的,再根据分值的大小对集合 ...
- Python实现猜拳小游戏的多种方式
简介 猜拳小游戏是一个经典的小游戏项目,也是初学者学习编程的必要练手题目之一.在 Python 中,我们可以使用多种方式来实现一个简单的猜拳小游戏. 本文将依次介绍六种Python实现猜拳小游戏的方法 ...