
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"
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:
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