Separar testes de unidade e testes de integração em Go

97

Existe uma prática recomendada estabelecida para separar testes de unidade e testes de integração em GoLang (testemunhar)? Eu tenho uma mistura de testes de unidade (que não dependem de nenhum recurso externo e, portanto, são executados muito rápido) e testes de integração (que dependem de quaisquer recursos externos e, portanto, são executados mais lentamente). Então, eu quero ser capaz de controlar se devo ou não incluir os testes de integração quando eu disser go test.

A técnica mais direta parece ser definir um sinalizador -integrate em principal:

var runIntegrationTests = flag.Bool("integration", false
    , "Run the integration tests (in addition to the unit tests)")

E, em seguida, adicionar uma instrução if no início de cada teste de integração:

if !*runIntegrationTests {
    this.T().Skip("To run this test, use: go test -integration")
}

É o melhor que posso fazer? Pesquisei a documentação do testify para ver se há talvez uma convenção de nomenclatura ou algo que faça isso para mim, mas não encontrei nada. Estou esquecendo de algo?

Craig Jones
fonte
2
Acho que o stdlib usa -short para desabilitar testes que atingem a rede (e outras coisas demoradas também). Caso contrário, sua solução parece boa.
Volker,
-short é uma boa opção, assim como seus sinalizadores de construção personalizados, mas seus sinalizadores não precisam estar no principal. se você definir a var como var integration = flag.Bool("integration", true, "Enable integration testing.")fora de uma função, a variável aparecerá no escopo do pacote e o sinalizador funcionará corretamente
Atifm

Respostas:

155

@ Ainar-G sugere vários ótimos padrões para separar testes.

Este conjunto de práticas Go do SoundCloud recomenda o uso de tags de compilação ( descritas na seção "Restrições de compilação" do pacote de compilação ) para selecionar quais testes executar:

Escreva um integration_test.go e dê a ele uma tag de construção de integração. Defina sinalizadores (globais) para itens como endereços de serviço e strings de conexão e use-os em seus testes.

// +build integration

var fooAddr = flag.String(...)

func TestToo(t *testing.T) {
    f, err := foo.Connect(*fooAddr)
    // ...
}

ir teste leva marcas de construção como construir movimento, de modo que você pode chamar go test -tags=integration. Ele também sintetiza um pacote principal que chama flag.Parse, de forma que quaisquer sinalizadores declarados e visíveis serão processados ​​e disponibilizados para seus testes.

Como uma opção semelhante, você também pode ter testes de integração executados por padrão usando uma condição de construção // +build !unite, em seguida, desabilitá-los sob demanda executando go test -tags=unit.

comentários @adamc:

Para qualquer outra pessoa que tente usar tags de construção, é importante que o // +build testcomentário seja a primeira linha em seu arquivo e que você inclua uma linha em branco após o comentário, caso contrário, o -tagscomando irá ignorar a diretiva.

Além disso, a tag usada no comentário de construção não pode ter um traço, embora sublinhados sejam permitidos. Por exemplo, // +build unit-testsnão funcionará, mas // +build unit_testssim.

Alex
fonte
1
Eu tenho usado isso há algum tempo e é de longe a abordagem mais lógica e simples.
Ory Band
1
se você tiver testes de unidade no mesmo pacote, você precisa definir // + build unitem testes de unidades e usar -tag unit para executar os testes
LeoCBS
2
@ Tyler.z.yang você pode fornecer um link ou mais detalhes sobre a suspensão de uso de tags? Eu não encontrei essas informações. Estou usando tags com go1.8 para a forma descrita na resposta e também para tipos e funções de simulação em testes. É uma boa alternativa para interfaces, eu acho.
Alexander I.Grafov
2
Para qualquer outra pessoa que tente usar tags de construção, é importante que o // +buildcomentário de teste seja a primeira linha em seu arquivo e que você inclua uma linha em branco após o comentário, caso contrário, o -tagscomando irá ignorar a diretiva. Além disso, a tag usada no comentário de construção não pode ter um traço, embora sublinhados sejam permitidos. Por exemplo, // +build unit-testsnão funcionará, enquanto que // +build unit_testswill
adamc
6
Como lidar com curingas? go test -tags=integration ./...não funciona, ele ignora a tag
Erika Dsouza
53

Para elaborar o meu comentário para @ excelente resposta de Ainar-G, durante o ano passado eu tenho usado a combinação de -shortcom Integrationconvenção de nomenclatura para alcançar o melhor dos dois mundos.

Unidade e integração testa harmonia, no mesmo arquivo

Bandeiras de construção anteriormente me obrigou a ter vários arquivos ( services_test.go, services_integration_test.go, etc).

Em vez disso, pegue este exemplo abaixo, em que os dois primeiros são testes de unidade e eu tenho um teste de integração no final:

package services

import "testing"

func TestServiceFunc(t *testing.T) {
    t.Parallel()
    ...
}

func TestInvalidServiceFunc3(t *testing.T) {
    t.Parallel()
    ...
}

func TestPostgresVersionIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }
    ...
}

Observe que o último teste tem a convenção de:

  1. usando Integrationno nome do teste.
  2. verificar se está executando sob a -shortdiretiva de flag.

Basicamente, a especificação é: "escreva todos os testes normalmente. Se for um teste de longa execução ou um teste de integração, siga esta convenção de nomenclatura e verifique -shortse é bom com seus colegas."

Execute apenas testes de unidade:

go test -v -short

isso fornece um bom conjunto de mensagens como:

=== RUN   TestPostgresVersionIntegration
--- SKIP: TestPostgresVersionIntegration (0.00s)
        service_test.go:138: skipping integration test

Execute testes de integração apenas:

go test -run Integration

Isso executa apenas os testes de integração. Útil para canários de teste de fumaça em produção.

Obviamente, a desvantagem dessa abordagem é que, se alguém executar go test, sem o -shortsinalizador, o padrão será executar todos os testes - testes de unidade e integração.

Na realidade, se seu projeto é grande o suficiente para ter testes de unidade e integração, então provavelmente você está usando um Makefileonde pode ter diretivas simples para usar go test -shortnele. Ou simplesmente coloque-o em seu README.mdarquivo e encerrar o dia.

eduncan911
fonte
3
amo a simplicidade
Jacob Stanley
Você cria um pacote separado para que esse teste acesse apenas as partes públicas do pacote? Ou tudo misturado?
Dr.eel
@ Dr.eel Bem, isso é OT da resposta. Mas pessoalmente, eu prefiro os dois: um nome de pacote diferente para os testes para que eu possa importmeu pacote e testá-lo, o que acaba me mostrando como minha API se parece para os outros. Em seguida, sigo com qualquer lógica restante que precise ser coberta como nomes de pacotes de teste internos.
eduncan911 01 de
@ eduncan911 Obrigado pela resposta! Pelo que entendi aqui package servicescontém um software de teste de integração, então para testar a API do pacote como uma caixa preta, devemos nomeá-lo de outra forma package services_integration_test, não nos dará a chance de trabalhar com estruturas internas. Portanto, o pacote para testes de unidade (acesso interno) deve ser nomeado package services. É assim?
Dr.eel 01 de
Correto, sim. Aqui está um exemplo claro de como faço isso: github.com/eduncan911/podcast (observe 100% de cobertura de código, usando exemplos)
eduncan911 01 de
50

Eu vejo três soluções possíveis. O primeiro é usar o modo curto para testes de unidade. Portanto, você usaria go test -shortcom testes de unidade e o mesmo, mas sem o -shortsinalizador para executar seus testes de integração também. A biblioteca padrão usa o modo curto para pular testes de longa execução ou torná-los mais rápidos fornecendo dados mais simples.

A segunda é usar uma convenção e chamar seus testes TestUnitFooou TestIntegrationFooe, em seguida, usar o -runsinalizador de teste para indicar quais testes executar. Portanto, você usaria go test -run 'Unit'para testes de unidade e go test -run 'Integration'para testes de integração.

A terceira opção é usar uma variável de ambiente e obtê-la em sua configuração de testes com os.Getenv. Então você usaria simples go testpara testes de unidade e FOO_TEST_INTEGRATION=true go testpara testes de integração.

Eu pessoalmente prefiro a -shortsolução, pois é mais simples e é usada na biblioteca padrão, então parece que é uma maneira de separar / simplificar testes de longa duração. Mas as soluções -rune os.Getenvoferecem mais flexibilidade (também é necessário mais cuidado, pois regexps estão envolvidos -run).

Ainar-G
fonte
1
observe que os executores de teste da comunidade (por exemplo Tester-Go) comuns a IDEs (Atom, Sublime, etc) têm a opção embutida de rodar com -shortflag, junto com -coveragee outros. portanto, uso uma combinação de Integração no nome do teste, juntamente com as if testing.Short()verificações desses testes. permite-me ter o melhor dos dois mundos: executar com -shortIDEs e executar explicitamente apenas testes de integração comgo test -run "Integration"
eduncan911
5

Eu estava tentando encontrar uma solução para o mesmo recentemente. Estes foram meus critérios:

  • A solução deve ser universal
  • Nenhum pacote separado para testes de integração
  • A separação deve ser completa (devo ser capaz de executar apenas testes de integração )
  • Nenhuma convenção de nomenclatura especial para testes de integração
  • Deve funcionar bem sem ferramentas adicionais

As soluções mencionadas acima (sinalizador personalizado, tag de construção personalizada, variáveis ​​de ambiente) realmente não satisfaziam todos os critérios acima, então, depois de pesquisar um pouco e brincar, descobri esta solução:

package main

import (
    "flag"
    "regexp"
    "testing"
)

func TestIntegration(t *testing.T) {
    if m := flag.Lookup("test.run").Value.String(); m == "" || !regexp.MustCompile(m).MatchString(t.Name()) {
        t.Skip("skipping as execution was not requested explicitly using go test -run")
    }

    t.Parallel()

    t.Run("HelloWorld", testHelloWorld)
    t.Run("SayHello", testSayHello)
}

A implementação é direta e mínima. Embora exija uma convenção simples para testes, é menos sujeito a erros. Melhorias adicionais poderiam ser exportar o código para uma função auxiliar.

Uso

Execute testes de integração apenas em todos os pacotes em um projeto:

go test -v ./... -run ^TestIntegration$

Execute todos os testes ( regulares e integração):

go test -v ./... -run .\*

Execute apenas testes regulares :

go test -v ./...

Esta solução funciona bem sem ferramentas, mas um Makefile ou alguns aliases podem torná-la mais fácil de usar. Ele também pode ser facilmente integrado a qualquer IDE que suporte a execução de testes go.

O exemplo completo pode ser encontrado aqui: https://github.com/sagikazarmark/modern-go-application

mark.sagikazar
fonte