单元测试
需要Mock的外部依赖:
- 通过GORM框架进行的数据库操作
- Gin框架实现的HTTP请求与响应
数据库操作
在各个路由的Handler中,我们经常使用GORM进行数据库操作,因此单元测试需要检验数据库操作是否复合预期。由于GORM在内部拼接SQL语句并发送给数据库处理,我们可以使用 go-sqlmock 模拟数据库操作,并对GORM输出给数据库的SQL语句进行检查。
生成模拟的*sql.DB连接:
mockDb, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("Failed to create sqlmock: %s", err)
}
defer mockDb.Close()
由于被测试的函数通常需要接受一个*gorm.DB对象作为参数,或通过*gin.Context获取*gorm.DB对象,我们需要从模拟的数据库连接构建*gorm.DB:
gormDb, err := gorm.Open(mysql.New(mysql.Config{
Conn: mockDb,
}), &gorm.Config{})
if err != nil {
t.Fatalf("Failed to create gorm database: %s", err)
}
这个*gorm.DB对象可以被传递给被测试的函数用于测试。
其它相关框架:
- go-mocket (已停止维护)
HTTP请求与响应
根据 Gin 官方文档,我们可以通过标准库net/http/httptest对HTTP请求和响应进行单元测试。
首先需要构建*gin.Engine对象并配置需要测试的API路由:
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
return r
}
然后使用net/http构建测试的HTTP请求,并通过gin.Engine.ServeHTTP()模拟接收到的请求:
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPingRoute(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/ping", nil)
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
assert.Equal(t, "pong", w.Body.String())
}
其它相关框架:
- gofight (已停止维护)
目前方案存在的问题:
- Gin Handler中的错误不能在单元测试中获取,只能通过调试查看错误。
- go-sqlmock 直接匹配SQL语句太严格,任何对数据库操作的变更都需要更新单元测试。更好的方式是在数据库操作结束后检查期望发生的更改,但没有找到合适的框架。考虑其它方案:使用针对GORM的Mock框架
比较gofight与net/http
gofight 和 net/http/httptest 都为测试HTTP服务器逻辑提供了方法。然而,它们的方法和易用性可能会有所不同。
1. 使用简便性
-
gofight: 旨在简化Go中的HTTP测试,
gofight提供了流畅且可链式的API。它抽象了许多底层细节,使测试读写变得简单,尤其是对于那些可能不熟悉http包复杂性的人。 -
net/http/httptest: 如Gin的文档中所示,此包为测试HTTP处理程序在Go中提供了更直接和标准的方法。用户获得更多控制权,但可能稍显繁琐。
2. 灵活性
- gofight: 支持许多用于设置header、cookies、查询参数和请求体的内置方法。但是,如果你需要对HTTP请求进行非常详细的定制,可能会遇到一些限制。
- net/http/httptest: 提供了更底层的方法,能够完全控制请求和响应对象。如果你需要进行非标准或高度自定义的请求,这种方法可能更合适。
3. 依赖性:
- gofight: 它是外部依赖项。如果您希望最小化外部包或担心长期维护,这可能是一个问题。
- net/http/httptest: 它是Go标准库的一部分。使用它可以确保您拥有一个稳定且维护良好的工具,而无需添加外部依赖项。
5. 与测试库的集成:
- gofight: 与
github.com/stretchr/testify/assert这样的流行断言库完美集成。 - net/http/httptest: 由于它是标准库,它可以轻松集成到Go的任何断言或测试库中。
结论:
- 追求简单和可读性: 如果你正在寻找一种简洁且可读的方式来编写HTTP测试,尤其是当你有很多路由需要使用各种参数进行测试时,
gofight是一个极好的选择。 - 追求控制和简约: 如果您更喜欢更接近Go的标准库、最小化依赖项或需要更多地控制请求/响应细节,
net/http/httptest更值得推荐。
CI/CD
我们的大项目开发流程通过GitHub进行管理,使用GitHub Actions配置CI/CD系统,能够在main分支被更改,或有PR合并时自动运行单元测试,确保已有的功能不被破坏。