使用数据库编写集成测试对于 web 应用程序开发至关重要,因为它增强了我们对代码的信心并确保我们的应用程序按预期工作。然而,为这些测试准备模拟数据可能具有挑战性,特别是在 go 中,它缺乏用于此任务的内置方法或标准库。本文介绍了 gofacto 库,它简化了构建模拟数据并将其插入数据库以进行 go 集成测试的过程。
什么是 gofacto?
gofacto 是一个 go 库,可简化模拟数据的创建和插入数据库。它提供了一种直观的方法来定义数据模式和有效处理数据库插入。借助 gofacto,开发人员可以快速准备测试数据,而无需编写大量样板代码,从而使他们能够专注于编写有意义的测试。
使用 gofacto 之前
让我们看看用 go 编写数据库集成测试时通常会做什么。假设我们在数据库中有一个名为 users 的表,它具有以下架构:
create table users ( id int primary key, name varchar(255) not null, email varchar(255) not null );
假设我们要测试一个名为 getuserbyid 的函数,该函数通过用户 id 从用户表中检索用户。为了测试这个功能,我们需要在测试这个功能之前在数据库中准备一些模拟数据。我们通常是这样做的:
type user struct { id int gender string name string email string } // build and insert mock user mockuser := user{ id: 1, gender: "male", name: "alice", email: "aaa@gmail.com", } err := inserttodb(mockuser) // action result, err := getuserbyid(mockuser.id) // assertion // ...
inserttodb 是一个将模拟数据插入数据库的函数。如果我们使用原始 sql 查询,可能会很复杂。
这种方法似乎易于管理,因为架构很简单,而且我们只处理一张表。
让我们看看处理两个表、用户和帖子时的情况。每个用户可以有多个帖子,表之间的关系是通过 posts 表中的 user_id 字段建立的。
create table posts ( id int primary key, user_id int not null, title varchar(255) not null, content text not null, foreign key (user_id) references users(id) );
假设我们要测试一个名为 getpostsbyuserid 的函数,该函数根据用户 id 从 posts 表中检索所有帖子。
type post struct { id int userid int title string content string } // build and insert mock user mockuser := user{ id: 1, gender: "male", name: "alice", email: "aaa@gmail.com", } err := inserttodb(mockuser) // build and insert mock post mockpost1 := post{ id: 1, userid: mockuser.id, // manually set the foreign key title: "post 1", content: "content 1", } err = inserttodb(mockpost1) // build and insert mock post mockpost2 := post{ id: 2, userid: mockuser.id, // manually set the foreign key title: "post 2", content: "content 2", } err = inserttodb(mockpost2) // action result, err := getpostsbyuserid(mockuser.id) // assertion // ...
我们首先创建一个用户,然后为该用户创建两个帖子。与前面的示例相比,它变得更加复杂,因为我们处理两个表并建立它们之间的关系。
如果我们想为不同的用户创建多个帖子怎么办?
我们需要为每个帖子创建一个用户,这需要更多代码。
// build and insert mock user mockuser1 := user{ id: 1, gender: "male", name: "alice", email: "aaa@gmail.com", } err := inserttodb(mockuser1) // build and insert mock user mockuser2 := user{ id: 2, gender: "female", name: "bob", email: "bbb@gmail.com", } err = inserttodb(mockuser2) // build and insert mock post mockpost1 := post{ id: 1, userid: mockuser1.id, // manually set the foreign key title: "post 1", content: "content 1", } err = inserttodb(mockpost1) // build and insert mock post mockpost2 := post{ id: 2, userid: mockuser2.id, // manually set the foreign key title: "post 2", content: "content 2", } err = inserttodb(mockpost2) // action result, err := getpostsbyuserid(mockuser1.id) // assertion // ...
当我们需要使用不同的用户和帖子创建多个模拟数据时,它会变得更加复杂且容易出错。
另请注意,我们仅使用简单的模式进行演示,实际应用中的代码会更加复杂。
存在哪些问题?
上面的例子中,存在一些问题:
编写大量样板代码来准备数据库中的模拟数据
有时候,我们并不关心字段的值是多少,我们只需要确保每个字段都有正确的值。
对模拟数据中的 id 值进行硬编码
在模拟数据中硬编码 id 的值不是一个好习惯,因为 id 通常会自动递增 数据库.
手动建立表之间的关系
这使得测试代码变得繁琐且容易出错,尤其是在使用多个相关表创建模拟数据时。
使用 gofacto
现在,让我们看看gofacto库如何帮助我们解决上述问题,让整个过程变得更加简单。
让我们看一下用户表的第一个示例。
// initialize a factory with user struct (also use `withdb` to pass the database connection) f := gofacto.new(user{}).withdb(db) // build and insert mock user mockuser, err := f.build(ctx).insert() // action result, err := getuserbyid(mockuser.id) // assertion // ...
为了使用 gofacto,我们首先使用 new 函数与 user 一起初始化一个新工厂。因为我们需要将数据插入数据库,所以使用withdb将数据库连接传递给工厂。
然后,我们使用 build 函数来构建模拟数据。 insert函数将mock数据插入数据库,并以自增id返回已插入数据库的mock数据。
请注意,模拟数据的所有字段都是默认随机生成的。在这种情况下没关系,因为我们不关心字段的值。
如果我们想要指定字段的值,我们可以使用 overwrite 函数来设置字段的值。
mockuser, err := f.build(ctx).overwrite(user{gender: "male"}).insert() // mockuser.gender == "male"
使用overwrite功能时,我们只需要指定要覆盖的字段即可。其他字段将照常随机生成。
让我们看看我们想要用一个用户创建多个帖子的情况。
为了让 gofacto 知道表之间的关系,我们需要在结构体中定义正确的标签。
type post struct { id int userid int `gofacto:"foreignkey,struct:user"` title string content string }
标签告诉 gofacto userid 字段是引用 user 结构体的 id 字段的外键。
现在,我们可以轻松地用一个用户创建多个帖子。
mockuser := user{} mockposts, err := f.buildlist(ctx, 2).withone(&mockuser).insert() // must pass pointer to the struct to `withone` // mockposts[0].userid == mockuser.id // mockposts[1].userid == mockuser.id // action result, err := getpostsbyuserid(mockuser.id) // assertion // ...
为了创建多个帖子,我们使用 buildlist 函数以及我们想要创建的帖子数量。然后,我们使用 withone 函数来指定所有帖子都属于一个用户。 insert 函数返回已插入数据库且 id 自动递增的帖子列表。
gofacto 库确保所有字段都正确随机设置,并且表之间的关系正确建立。
让我们看看我们想要为不同用户创建多个帖子的情况。
mockUser1 := User{} mockUser2 := User{} mockPosts, err := f.BuildList(ctx, 2).WithMany([]interface{}{&mockUser1, &mockUser2}).Insert() // mockPosts[0].UserID == mockUser1.ID // mockPosts[1].UserID == mockUser2.ID // action result, err := getPostsByUserID(mockUser1.ID) // assertion // ...
我们使用 withmany 函数来指定每个帖子与不同的用户关联。
概括
我们已经看到了 gofacto 如何简化在 go 中编写数据库集成测试。它减少了样板代码,使准备多个表的模拟数据并在它们之间建立关系变得更加容易。最重要的是,gofacto 抽象了准备模拟数据的复杂性,使开发人员能够专注于编写有意义的测试。 要开始在 go 项目中使用 gofacto,请访问 github 存储库以获取安装说明和更详细的文档。
反馈和进一步发展
作为一名新的库开发人员,我很想听听您对 gofacto 的想法!如有任何反馈、建议或批评,我们表示赞赏。如果您在 go 项目中使用它,请分享您的经验。发现错误或有想法?在 gofacto github 存储库上打开问题。想贡献代码吗?欢迎拉取请求!您的反馈和贡献将有助于改进 gofacto 并使 go 社区受益。感谢您查看!
以上就是使用 gofacto 简化 Go 集成测试:强大的模拟数据工厂的详细内容,更多请关注本网内其它相关文章!