为什么你写的代码总是有 Bug?用它来保证 Go 代码质量( 三 )

代码清单 3 展示了 Truncate 函数 。顾名思义,它用于删除 SeedLists 和 SeedItems 函数插入的所有数据 。
使用 testing.M 创建 TestMain使用便于 填充/清除 数据库的软件包后,该集中精力配置以运行真正的集成测试了 。Go 自带的测试工具可以让你在 TestMain 函数中定义需要的行为,在测试函数执行前执行 。
代码清单 4func TestMain(m *testing.M) {    os.Exit(testMain(m))}代码清单 4 是 TestMain 函数,它在所有集成测试之前执行 。在 23 行,叫做 testMain 的未导出的函数被 os.Exit 调用 。这样做是为了 testMain 可以执行其中的延迟函数,并且仍可以在 os.Exit 调用内部设置适当的整数值 。以下是 testMain 函数的实现 。
代码清单 5func testMain(m *testing.M) int {    dbc, err := testdb.Open()    if err != nil {        log.WithError(err).Info("create test database connection")        return 1    }    defer dbc.Close()    a = handlers.NewApplication(dbc)    return m.Run()}在代码清单 5 中,你可以看到 testMain 只有 8 行代码 。28 行,函数调用 testdb.Open() 开始建立数据库连接 。此调用的配置参数在 testdb 包中设置为常量 。重要的是要注意,如果测试用的数据库未运行,调用 Opne 连接数据库会失败 。该测试数据库是由 docker-compose 创建提供的,详细说明在本系列的第 1 部分中(单击 这里[3] 阅读第 1 部分) 。
成功连接测试数据库后,连接将传递给 handlers.NewApplication(),并且此函数的返回值用于初始化的包级变量 *handlers.Application 类型 。handlers.Application 类型是这个项目自定义的结构体,有用于 http.Handler 接口的字段,以简化 Web 服务的路由以及对已创建的数据库连接的引用 。
现在,应用程序值已初始化,可以调用 m.Run 来执行所有测试函数 。对 m.Run 的调用处于阻塞状态,直到所有确定要运行的测试函数都执行完之后,该调用才会返回 。非零退出代码表示失败,0 表示成功 。
编写 Web 服务的集成测试集成测试将多个代码单元以及所有集成服务(例如数据库)组合在一起,并测试各个单元的功能以及各个单元之间的关系 。为 Web 服务编写集成测试通常意味着每个集成测试的所有入口点都是一个路由 。http.Handler 接口是任何 Web 服务的必需组件,它包含的 ServeHTTP 函数使我们能够利用应用程序中定义的路由 。
在 Web 服务的集成测试中,构建初始化数据并且以 Go 类型返回初始数据,对返回的响应体的结构进行断言非常有用 。在接下来的代码清单中,我将一个典型的 API 路由集成测试分解成几个不同的部分 。第一步是使用代码清单 1 和代码清单 2 中定义的种子数据 。
清单 6func Test_getItems(t *testing.T) {    defer func() {        if err := testdb.Truncate(a.DB); err != nil {            t.Errorf("error truncating test database tables: %v", err)        }    }()    expectedLists, err := testdb.SeedLists(a.DB)    if err != nil {        t.Fatalf("error seeding lists: %v", err)    }    expectedItems, err := testdb.SeedItems(a.DB, expectedLists)    if err != nil {        t.Fatalf("error seeding items: %v", err)    }}【为什么你写的代码总是有 Bug?用它来保证 Go 代码质量】在获取种子数据失败前,必须设置延迟函数清理数据库,这样,无论函数失败与否,测试结束后保证数据库是干净的 。然后,调用 testdb 中的种子函数(testdb.SeedLists 和 testdb.SeedItems )构造初始数据,并获取他们的返回值作为预期值,以便在集成测试中与实际路由请求结果(真实值)做对比 。如果这两个种子函数中的任何一个失败,测试就会调用 t.Fatalf。
清单 7// Application is the struct that contains the server handler as well as// any references to services that the application needs.type Application struct {    DB      *sqlx.DB    handler http.Handler}// ServeHTTP implements the http.Handler interface for the Application type.func (a *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {    a.handler.ServeHTTP(w, r)}


推荐阅读