Build an automated API testing tool using the Go programming language.
I developed this tool using Go language, mainly because Go allows for foolproof cross-compilation, making the generated executable files directly uploadable to the server for immediate execution, which is very convenient.
目录12 个章节
Preface
The previous article mentioned that I also developed an independent automated testing tool, which can test based on OpenAPI documentation and output a test report after completion. The report includes details such as whether each API passed the test and the response time.
I developed this tool using the Go language, mainly because Go allows for a foolproof cross-compilation process. The generated executable file can be directly uploaded to the server and run, which is very convenient.
PS: Writing in Go is genuinely frustrating! It feels like the syntax has many awkward parts, but the build process is incredibly satisfying—just impossible to resist 😂
To avoid making the article too long, this piece will first introduce the components used. Detailed implementation and the part about parsing OpenAPI documentation to generate test configurations will be covered in subsequent articles.
Network Requests
The net/http package in the standard library provides the functionality to send HTTP requests. After obtaining the data, the json.Unmarshal function is used to parse the JSON data. This package is relatively low-level and sufficient for simple network requests. However, I still prefer to choose a more user-friendly component.
Resty is a simple yet powerful Go HTTP client with a chainable API that makes it easy to send HTTP requests and handle JSON data. It offers a rich set of features, including automatic retries, timeout settings, request and response logging, and more. You can use Resty to send various types of requests like GET, POST, PUT, DELETE, etc., and it can automatically parse the JSON response data into Go structs.
The v2 version is now available, supporting HTTP/2, WebSocket, Cookie operations, and providing a more concise and user-friendly API.
Project URL: https://github.com/go-resty/resty
Works pretty well.
GET Method
import "github.com/go-resty/resty/v2"
req := c.RestyClient.R().SetHeader("Authorization", "token "+c.AuthToken)
req.SetQueryParams(map[string]string{
"year": "2024",
})
resp, err = req.Get("path")
POST Method
req.SetBody(map[string]string{
"year": "2024",
})
resp, err = req.Get("path")
The parameter for SetBody is of type interface{}, allowing a wide variety of types to be passed. In this example, I passed a map, just like for the GET request. In practice, however, it is more common to pass a struct.
Logging Component
I previously used Go's built-in log, but it seemed to have very few features and lacked log levels—could this even be called a logging library...
Then I found the highly starred logrus library on GitHub, but it felt like a rather outdated library that wasn't very user-friendly. I couldn't find a suitable formatter either, and after checking the project's homepage, I discovered the library has been put into retirement...
It told me to check out, for example, Zerolog, Zap, and Apex.
Logrus is in maintenance-mode. We will not be introducing new features. It's simply too hard to do in a way that won't break many people's projects, which is the last thing you want from your Logging library (again...).
Project URL: https://github.com/sirupsen/logrus
So, in the end, I ended up using Uber's logging library go.uber.org/zap.
Logrus Usage & Configuration
Although I switched to zap later, I'm still documenting how to use logrus.
I've tried most of the third-party formatters listed on the project homepage, and only this nested-logrus-formatter works well.
The following configuration implements outputting logs to both the console and a file simultaneously.
import (
nested "github.com/antonfisher/nested-logrus-formatter"
"github.com/sirupsen/logrus"
"os"
)
func initLogger() *os.File {
logger.SetLevel(logrus.DebugLevel)
logger.SetReportCaller(true)
logger.SetFormatter(&nested.Formatter{})
// Create a file for log output
file, err := os.OpenFile("logfile.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logger.Fatalf("Failed to open log file: %v", err)
}
// Create a multi-writer to output logs to both console and file
mw := io.MultiWriter(os.Stdout, file)
// Add the Hook to the Logger
logger.Out = mw
return file
}
func main() {
file := initLogger()
defer func(file *os.File) {
err := file.Close()
if err != nil {
fmt.Println(err)
}
}(file)
}
zap Usage & Configuration
zap is much more user-friendly than logrus and works out of the box. Combined with zapcore, it can be configured for multiple outputs, supports log file splitting by size, and can even integrate with other log collection platforms. It has essentially reached the level of a modern logging component...
As before, it achieves simultaneous logging to both the console and a file.
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
func buildLogger() *zap.SugaredLogger {
config := zap.NewProductionEncoderConfig()
config.EncodeTime = zapcore.ISO8601TimeEncoder
consoleEncoder := zapcore.NewConsoleEncoder(config)
fileEncoder := zapcore.NewJSONEncoder(config)
logFile, _ := os.OpenFile("./log-test-zap.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 06666)
tee := zapcore.NewTee(
zapcore.NewCore(fileEncoder, zapcore.AddSync(logFile), zap.DebugLevel),
zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zap.DebugLevel),
)
var zapLogging = zap.New(
tee,
zap.AddCaller(),
zap.AddStacktrace(zapcore.ErrorLevel),
)
var logger = zapLogging.Sugar()
return logger
}
func main() {
logger := buildLogger()
defer logger.Sync()
}
Swagger Component
Here I tried using go swagger.
Project repository: https://github.com/go-swagger/go-swagger
It can be installed using scoop.
scoop install go-swagger
My experience was average, not as good as swagger code generator.
So I didn't continue exploring further.
PS: JetBrains IDEs include swagger code generator tools with very powerful features that can generate various code.
However, I implemented OpenAPI document parsing myself (which is more flexible), so I haven't used this powerful tool yet.
Excel Export
For Excel operations, I'm using this library: github.com/xuri/excelize/v2
I didn't pay much attention at first, but later discovered it was actually open-sourced by QiAnXin... Then I looked at the qax-os group and found that the several open-source projects they've released are all unrelated to security. Not sticking to their core business, guys! 😂
I didn't compare it with others. Seeing it had many stars, I just started using it directly.
It feels alright.
func exportTestReportsToExcel(testReports []*tester.Report, filename string) error {
// 创建一个新的 Excel 文件
f := excelize.NewFile()
// 创建一个名为 "测试报告" 的工作表
index, err := f.NewSheet("测试报告")
if err != nil {
return err
}
// 设置工作表列名
f.SetCellValue("测试报告", "A1", "接口名称")
f.SetCellValue("测试报告", "B1", "接口路径")
f.SetCellValue("测试报告", "C1", "测试是否通过")
f.SetCellValue("测试报告", "D1", "耗时(秒)")
// 遍历测试报告并在工作表中写入数据
for i, report := range testReports {
row := i + 2
f.SetCellValue("测试报告", fmt.Sprintf("A%d", row), report.ApiName)
f.SetCellValue("测试报告", fmt.Sprintf("B%d", row), report.ApiPath)
f.SetCellValue("测试报告", fmt.Sprintf("C%d", row), func() string {
if report.IsPassed {
return "是"
}
return "否"
}())
f.SetCellValue("测试报告", fmt.Sprintf("D%d", row), report.Elapsed.Seconds())
}
// 设置活动工作表
f.SetActiveSheet(index)
// 将 Excel 文件保存到磁盘
err = f.SaveAs(filename)
if err != nil {
return err
}
return nil
}
Rant
Ternary Expression (三元表达式)
I really want to complain about why Go doesn't have a ternary expression (三元表达式). Using anonymous functions is truly so cumbersome!!
It's said that the reason is because it's believed that ternary expressions can lead to a lot of clever code that's hard to understand, so Go doesn't plan to support it. That's throwing out the baby with the bathwater. 😂
But this doesn't stop me. I can write a function to simulate it. Also, it seems Go has updated its generics features now, so there's no longer a need to use interface for simulation.
func If[T any](condition bool, trueVal, falseVal T) T {
if condition {
return trueVal
}
return falseVal
}
When using it, it's simply:
result := If[string](report.IsPassed, "成功", "没通过")
It supports type inference, so the [string] can even be omitted.
Now, the anonymous function in the earlier Excel export code can be changed to this, making it much more concise!
f.SetCellValue("测试报告", fmt.Sprintf("C%d", row), If(report.IsPassed, "是", "否"))
Array Sorting
This isn't really a complaint, more like nitpicking—Go's sorting isn't the most convenient, but it's not difficult either.
You can sort by field using anonymous functions, which is pretty much the same as using function pointers in C—truly a C language with garbage collection
The data structure for the test report is as follows:
// Report test report
type Report struct {
ApiName string
ApiPath string
IsPassed bool
Elapsed time.Duration
Response *ApiResponse
}
If I want to sort a []*Report array, I can use the sort.Slice method:
// Sort by the Elapsed property in descending order
sort.Slice(testReports, func(i, j int) bool {
return testReports[i].Elapsed > testReports[j].Elapsed
})
In comparison, C#'s LINQ is so much more pleasant
testReports.Sort((a, b) => a - b);
Summary
That's it. A fairly simple little tool. Since I'm still in the Go beginner stage, I make sure to document each new library I use.
Reference
- Detailed Guide to Uber's Go Logging Library Zap - https://www.cnblogs.com/jiujuan/p/17304844.html
_EOF_
Build an automated API testing tool using the Go programming language.
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
当 CGO 遇见 Zig:一种更优雅的折腾方式,对比 GCC 后端
“GCC 赢了跑分,但 Zig 赢了人生。” (GCC wins the benchmark, but Zig wins the DX/Developer Experience.) Windows下cgo编译器从gcc换到zig?GCC快但环境配置可能比较麻烦,zig单文件轻量但编译慢且首跑延迟离谱。性能差距大吗?或许吧,但开发效率的提升值得折腾。
2026-04-14 · Go · 约 1 分钟
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
受够了满屏广告的转换工具,随手给命令行工具套了个 Go-Gin 的壳。本想图个清静,结果这群人直接把服务器跑崩了,目前只能被迫停机。代码丢在 GitHub 了,别指望我会去修那还没写完的登录功能。
2025-12-31 · Go · 约 1 分钟
使用go语言开发hive导出工具
作者弃bash、Python、C#,选Go写Hive导出工具,只因部署简单。Go的err处理烦人,但交叉编译省事。工具交互式,搞定导出。
2023-10-17 · Go · 约 1 分钟
绿联USB3.0拓展坞开箱
之前家里用的USB拓展坞是USB2.0的好像 这次我就没带过来 于是张罗着买个USB3.0以上的新拓展坞来用 看了下3.0和3.2价格差距有点多,只是用来插鼠标和键盘适配器的话,似乎不需要那么高的速度,所以入手了绿联3.0的拓展坞
2026-09-08 · 拓展坞 · 约 1 分钟
讨论
共 0 条加载评论中…