Testes de integração paralela do Postgresql no aplicativo GO


Testes de integração é um dos níveis da pirâmide de testes . Geralmente eles exigem mais tempo, porque neles não substituímos nada por simulações de componentes reais. Para reduzir o tempo para esses testes, podemos executá-los em paralelo. Aqui vou falar especificamente sobre esses testes para o Postgresql.

Idealmente, cada teste deve ser independente, para que não se afetem. Em outras palavras, cada função de teste tem seu próprio estado. Este é um bom sinal para usar testes paralelos. Para obter meu conjunto de dados pessoais para cada função de teste, criei uma função que, ao iniciar um teste, cria um circuito temporário, carrega dados nele e destrói o circuito após a conclusão do teste. Cada esquema criado contém um hash no nome para evitar conflitos de nome.


Função auxiliar


Vamos começar com uma função auxiliar para exibir erros nos testes. Tomei as funções auxiliares de Ben Johnson (Ben Johnson), o que me ajudou a salvar algumas linhas de código e a tornar meus erros mais claros e detalhados.

Dados de teste


Para executar o teste de integração do banco de dados, os dados de teste devem ser fornecidos. A ferramenta de teste Go oferece um bom suporte para carregar dados de teste de arquivos. Primeiro, vá em frente pula pastas chamadas "testdata". Em segundo lugar, quando você executa o "teste", ele altera a pasta atual para a pasta do pacote. Isso permite que você use o caminho relativo para a pasta testdata para carregar o conjunto de dados de teste.

Criando uma conexão com o banco de dados para o teste


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()) } } } 


Chamar “CreateTestDatabase” para criar uma conexão com o banco de dados de teste e criar um novo esquema de dados para os testes. Essa função retorna a conexão com o banco de dados, o nome do esquema criado e a função de limpeza para excluir esse esquema. Para um teste, é melhor falhar no teste do que retornar um erro ao chamador. (Nota: O retorno da função de limpeza é baseado na conversa Advanced Testing with Go de Mitchell Hashimoto ).

Download do conjunto de dados de teste


Eu usei os arquivos ".sql". Um (1) sql contém dados para uma (1) tabela. Isso inclui criar uma tabela e preenchê-la com dados. Todos os arquivos sql são armazenados na pasta "testdata". Aqui está um exemplo de arquivo sql.

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

E aqui está a parte intricada. Como cada função é executada em seu próprio esquema de dados exclusivo, não podemos simplesmente executar (escrever) uma consulta nesses arquivos sql. É necessário especificar o esquema antes dos nomes da tabela para criar uma tabela ou inserir dados no esquema temporário desejado. Por exemplo, um livro CREATE TABLE ... deve ser escrito como CREATE TABLE uniqueschema.book ... e um livro INSERT INTO ... precisa ser alterado para INSERT INTO uniqueschema.book .... Eu usei expressões regulares para modificar consultas antes de executar. Aqui está o código de download dos dados de teste:

 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() } } 


Criação de teste


Antes de iniciar cada teste, um banco de dados de teste com um nome exclusivo para o esquema será criado e a execução da função de limpeza para excluir esse esquema será adiada. O nome do esquema será inserido na solicitação no teste. O mais importante nesta implementação é que a conexão com o banco de dados deve ser personalizável para alterar a conexão do banco de dados real para a conexão com o banco de dados de teste. Adicione “t.Parallel ()” no início de cada função de teste para indicar ao ambiente de teste a necessidade de executar esse teste em paralelo.
Abaixo está o código completo:

 //            "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)) } 


Nota: Em "TestGetBooks", presumo que a consulta retornará 2 livros, como Eu forneci tantos dados de teste definidos em "testdata / book.sql", embora exista um teste de inserção acima. Se não compartilharmos o circuito entre os dois testes, "TestGetBooks" falhará, porque agora 3 linhas na tabela, 2 do teste e 1 do inserto de teste acima. Essa é a vantagem de circuitos separados para testes - seus dados são independentes e, portanto, os testes são independentes um do outro.

O exemplo do projeto que publiquei aqui no github . Você pode copiá-lo para si mesmo, executar o teste e ver o resultado.

Conclusão


Para o meu projeto, essa abordagem reduz o tempo de teste em 40 a 50%, em comparação com os testes sequenciais. Outra vantagem da execução de testes em paralelo é que podemos evitar alguns erros que podem ocorrer quando um aplicativo processa várias ações competitivas.

Tenha um bom teste.

- Foto de medium.com/kongkow-it-medan/parallel-database-integration-test-on-go-application-8706b150ee2e

Source: https://habr.com/ru/post/pt466459/


All Articles