卓越飞翔博客卓越飞翔博客

卓越飞翔 - 您值得收藏的技术分享站
技术文章76049本站已运行4318

使用 gofacto 简化 Go 集成测试:强大的模拟数据工厂

使用 gofacto 简化 go 集成测试:强大的模拟数据工厂

使用数据库编写集成测试对于 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 社区受益。感谢您查看!

卓越飞翔博客
上一篇: C++ 时间和日期函数的精辟解析
下一篇: 返回列表
留言与评论(共有 0 条评论)
   
验证码:
隐藏边栏