使用数据库编写集成测试对于 Web 应用程序开发至关重要,因为它增强了我们对代码的信心并确保我们的应用程序按预期工作。然而,为这些测试准备模拟数据可能具有挑战性,特别是在Go中,它缺乏用于此任务的内置方法或标准库。本文介绍了 gofacto 库,它简化了构建模拟数据并将其插入数据库以进行 Go 集成测试的过程。
gofacto 是一个 Go 库,它简化了模拟数据的创建和插入到数据库中的过程。它提供了一种直观的方法来定义数据模式并有效地处理数据库插入。借助 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 // ...
当我们需要使用不同的用户和帖子创建多个模拟数据时,它会变得更加复杂且容易出错。
另请注意,我们仅使用简单的模式进行演示,实际应用中的代码会更加复杂。
上面的例子中,存在一些问题:
现在,让我们看看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 }
The tag tells gofacto that the UserID field is a foreign key that references the ID field of the User struct.
Now, we can create multiple posts with one user easily.
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 // ...
In order to create multiple posts, we use BuildList function with the number of posts that we want to create. Then, we use WithOne function to specify that all the posts belong to one user. The Insert function returns a list of posts that have been inserted into the database with the auto-incremented ID.
gofacto library makes sure all the fields are correctly set randomly, and the relationship between the tables is correctly established.
Let's see the case where we want to create multiple posts with different users.
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 // ...
We use WithMany function to specify that each post is associated with a different user.
We've seen how gofacto simplifies writing integration tests with databases in Go. It reduces boilerplate code and makes it easier to prepare mock data with multiple tables and establish relationships between them. Most importantly, gofacto abstracts away the complexity of preparing mock data, allowing developers to focus on writing meaningful tests. To start using gofacto in your Go projects, visit the GitHub repository for installation instructions and more detailed documentation.
As a new library developer, I'd love to hear your thoughts on gofacto! Any feedback, advice or criticism is appreciated. If you use it in your Go projects, please share your experience. Found a bug or have an idea? Open an issue on the gofacto GitHub repo. Want to contribute code? Pull requests are welcome! Your feedback and contributions will help improve gofacto and benefit the Go community. Thanks for checking it out!
以上是使用 gofacto 简化 Go 集成测试:强大的模拟数据工厂的详细内容。更多信息请关注PHP中文网其他相关文章!