首页 > 解决方案 > Golang:即使在 db.Close() 上,集成测试也会命中同一个数据库实例

问题描述

我有一个测试套件,它使用内存中的 sqlite 实例来运行数据库查询。添加一些测试后,我突然开始在尝试执行插入的每一行中遇到大量“UNIQUE 约束失败...”错误。这使得我的所有测试看起来都在连接、写入和读取同一个数据库实例。这是测试数据库实例的生成方式

const DBProvider = "sqlite3"

// DBConnection is the connection string to use for testing
const DBConnection = "file::memory:?cache=shared"

// NewMigratedDB returns a new connection to a migrated database
func NewMigratedDB(provider string, connection string, models ...interface{}) (*gorm.DB, error) {
    db, err := gorm.Open(provider, connection)
    if err != nil {
        return nil, err
    }
    db = db.AutoMigrate(models...)
    if db.Error != nil {
        return nil, err
    }
    return db, nil
}

以下是它在测试中的使用方式 -

    db, err := test.NewMigratedDB(test.DBProvider, test.DBConnection, models...)
    defer db.Close()
    // read/write anything

如何对 NewMigratedDB 进行每次调用以生成不同的 SQLite 实例,该实例仅侦听来自实例化它的单元测试的查询

标签: sqlitegogo-gorm

解决方案


首先,查看您NewMigratedDB在测试中如何使用的完整示例会很有帮助。这是我根据我能看到的(和我看不到的)提出的建议。

设置命名内存数据库

您的每个单元测试似乎都使用 SQLite 数据库的相同内存副本。使用以下语法创建内存数据库的单独命名实例。如果您的测试并行运行,它们很可能共享同一个数据库。替换test1为每个并行运行的测试的唯一名称(有关更多信息,请参阅sqlite 文档)。

如果在单个进程中需要两个或多个不同但可共享的内存数据库,则mode=memory查询参数可以与 URI 文件名一起使用以创建命名的内存数据库

const DBConnection = "file:test1?mode=memory&cache=shared"

在测试之间打开和关闭

您还需要确保每次测试都初始化一个新的数据库,而不是在测试功能的顶部一次。例如:

func TestSomething(t *testing.T) {
    // Don't initialize it here
    
    t.Run("test1", func(t *testing.T) {
        db, err := test.NewMigratedDB(test.DBProvider, test.DBConnection, models...)
        defer db.Close()
        // Do something
    })
    t.Run("test2", func(t *testing.T) {
        db, err := test.NewMigratedDB(test.DBProvider, test.DBConnection, models...)
        defer db.Close()
        // Do something
    })
}

如果您在每次测试之前和之后打开和关闭,并且您没有任何同时运行的测试也连接到 sqlite,那么关闭连接应该清除 sqlite 数据库,并且您不需要使用命名的内存数据库作为下一次调用gorm.Open. 那里有很多“如果”,所以选择你的毒药。


推荐阅读