GO应用程序中的Postgresql并行集成测试


集成测试是测试金字塔的级别之一 。 通常他们需要更多时间,因为 在这些模型中,我们不会用真实组件的仿真来替代任何东西。 为了减少此类测试的时间,我们可以并行运行它们。 在这里,我将专门讨论有关Postgresql的测试。

理想情况下,每个测试都应该是独立的,因此它们不会互相影响。 换句话说,每个测试功能都有其自己的状态。 这是使用并行测试的好兆头。 为了获得每个测试功能的个人数据集,我创建了一个功能,该功能在启动测试时会创建一个临时电路,将数据加载到其中,并在测试完成后销毁该电路。 创建的每个模式在名称中都包含一个哈希,以防止名称冲突。


辅助功能


让我们从一个辅助函数开始,以显示测试中的错误。 我采用了本·约翰逊的助手功能(本·约翰逊),它帮助我节省了几行代码,并使我的错误更加清晰和详细。

测试数据


要运行数据库的集成测试,必须提供测试数据。 Go测试工具很好地支持从文件加载测试数据。 首先,转到build跳过名为“ testdata”的文件夹。 其次,当您运行“ go test”时,它将当前文件夹更改为package文件夹。 这使您可以使用testdata文件夹的相对路径来加载测试数据集。

为测试创建数据库连接


package database import ( "math/rand" "strconv" "testing" "time" _ "github.com/lib/pq" "database/sql" ) const ( dbPort = 5439 dbUser = "postgres" dbPassword = "postgres" dbName = "test" ) func CreateTestDatabase(t *testing.T) (*sql.DB, string, func()) { connectionString := fmt.Sprintf("port=%d user=%s password=%s dbname=%s sslmode=disable", dbPort, dbUser, dbPassword, dbName) db, dbErr := sql.Open("postgres", connectionString) if dbErr != nil { t.Fatalf("Fail to create database. %s", dbErr.Error()) } rand.Seed(time.Now().UnixNano()) schemaName := "test" + strconv.FormatInt(rand.Int63(), 10) _, err := db.Exec("CREATE SCHEMA " + schemaName) if err != nil { t.Fatalf("Fail to create schema. %s", err.Error()) } return db, schemaName, func() { _, err := db.Exec("DROP SCHEMA " + schemaName + " CASCADE") if err != nil { t.Fatalf("Fail to drop database. %s", err.Error()) } } } 


调用“ CreateTestDatabase”创建与测试数据库的连接,并为测试创建新的数据架构。 该函数返回数据库连接,创建的模式的名称以及用于删除该模式的清除函数。 对于测试,测试失败比向调用者返回错误要好。 (注意:清除功能的返回基于Mitchell Hashimoto的Go Talk高级测试 )。

下载测试数据集


我使用了“ .sql”文件。 一(1)个sql包含一(1)个表的数据。 它包括创建表并向其中填充数据。 所有sql文件都存储在“ testdata”文件夹中。 这是一个示例sql文件。

 CREATE TABLE book ( title character varying(50), author character varying(50) ); INSERT INTO book VALUES ('First Book','First Author'), ('Second Book','Second Author') ; 

这是复杂的部分。 由于每个函数都在其自己的唯一数据方案中运行,因此我们不能仅在这些sql文件中执行(写入)查询。 我们必须在表名称之前指定模式,以便创建表或将数据插入所需的临时模式。 例如,将CREATE TABLE本书...编写为CREATE TABLE uniqueschema.book ...,然后将INSERT INTO本书...更改为INSERT INTO uniqueschema.book...。 我在执行前使用正则表达式来修改查询。 这是测试数据下载代码:

 package datalayer import ( "bufio" "fmt" "io" "os" "regexp" "testing" "database/sql" "github.com/Hendra-Huang/databaseintegrationtest/testingutil" //     (   ,  79) ) //        var schemaPrefixRegexps = [...]*regexp.Regexp{ regexp.MustCompile(`(?i)(^CREATE SEQUENCE\s)(["\w]+)(.*)`), regexp.MustCompile(`(?i)(^CREATE TABLE\s)(["\w]+)(\s.+)`), regexp.MustCompile(`(?i)(^ALTER TABLE\s)(["\w]+)(\s.+)`), regexp.MustCompile(`(?i)(^UPDATE\s)(["\w]+)(\s.+)`), regexp.MustCompile(`(?i)(^INSERT INTO\s)(["\w]+)(\s.+)`), regexp.MustCompile(`(?i)(^DELETE FROM\s)(["\w]+)(.*)`), regexp.MustCompile(`(?i)(.+\sFROM\s)(["\w]+)(.*)`), regexp.MustCompile(`(?i)(\sJOIN\s)(["\w]+)(.*)`), } //      func addSchemaPrefix(schemaName, query string) string { prefixedQuery := query for _, re := range schemaPrefixRegexps { prefixedQuery = re.ReplaceAllString(prefixedQuery, fmt.Sprintf("${1}%s.${2}${3}", schemaName)) } return prefixedQuery } func loadTestData(t *testing.T, db *sql.DB, schemaName string, testDataNames ...string) { for _, testDataName := range testDataNames { file, err := os.Open(fmt.Sprintf("./testdata/%s.sql", testDataName)) testingutil.Ok(t, err) reader := bufio.NewReader(file) var query string for { line, err := reader.ReadString('\n') if err == io.EOF { break } testingutil.Ok(t, err) line = line[:len(line)-1] if line == "" { query = addSchemaPrefix(schemaName, query) _, err := db.Exec(query) testingutil.Ok(t, err) query = "" } query += line } file.Close() } } 


测试创建


在开始每个测试之前,将创建一个具有该方案唯一名称的测试数据库,并且将推迟执行清除功能以删除该方案。 模式名称将被插入测试中的请求中。 此实现中最重要的事情是,必须自定义与数据库的连接,才能将实际数据库的连接更改为与测试数据库的连接。 在每个测试函数的开头添加“ t.Parallel()”,以指示测试环境需要并行运行此测试。
下面是完整的代码:

 //            "integration" (. build flags) // +build integration package datalayer import ( "context" "testing" "github.com/Hendra-Huang/databaseintegrationtest/database" "github.com/Hendra-Huang/databaseintegrationtest/testingutil" ) func TestInsertBook(t *testing.T) { t.Parallel() db, schemaName, cleanup := database.CreateTestDatabase(t) defer cleanup() loadTestData(t, db, schemaName, "book") // will load data which the filename is book title := "New title" author := "New author" // those 2 lines code below are not a good practice // but it is intentional to keep the focus only on integration test part // the important part is database connection has to be configurable insertBookQuery = addSchemaPrefix(schemaName, insertBookQuery) // override the query and add schema to the query err := InsertBook(context.Background(), db, title, author) // will execute insertBookQuery with the provided connection testingutil.Ok(t, err) } func TestGetBooks(t *testing.T) { t.Parallel() db, schemaName, cleanup := database.CreateTestDatabase(t) defer cleanup() loadTestData(t, db, schemaName, "book") getBooksQuery = addSchemaPrefix(schemaName, getBooksQuery) books, err := GetBooks(context.Background(), db) testingutil.Ok(t, err) testingutil.Equals(t, 2, len(books)) } 


注意:在“ TestGetBooks”下,我假设查询将返回2本书,因为 尽管上面有插入测试,但我在“ testdata / book.sql”中给出了如此多的测试数据集。 如果我们在两个测试之间不共享电路,则“ TestGetBooks”将失败,因为 现在表格中的3行,来自测试的2行,来自上方测试插入的1行。 这是用于单独测试的电路的优势-它们的数据是独立的,因此测试彼此独立。

我在这里github发布的项目示例。 您可以将其复制到自己,运行测试并查看结果。

结论


对于我的项目,与顺序测试相比,这种方法将测试时间减少了40–50%。 并行运行测试的另一个优点是,我们可以避免在应用程序处理多个竞争性操作时可能发生的一些错误。

有一个很好的测试。

-图片来自medium.com/kongkow-it-medan/parallel-database-integration-test-on-go-application-8706b150ee2e

Source: https://habr.com/ru/post/zh-CN466459/


All Articles