大项目开发中的单元测试 | 青训营

171 阅读4分钟

单元测试

需要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对象可以被传递给被测试的函数用于测试。

其它相关框架:

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())
}

其它相关框架:

目前方案存在的问题:

  • Gin Handler中的错误不能在单元测试中获取,只能通过调试查看错误。
  • go-sqlmock 直接匹配SQL语句太严格,任何对数据库操作的变更都需要更新单元测试。更好的方式是在数据库操作结束后检查期望发生的更改,但没有找到合适的框架。考虑其它方案:使用针对GORM的Mock框架

比较gofight与net/http

gofightnet/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合并时自动运行单元测试,确保已有的功能不被破坏。