Como posso fazer a configuração de teste usando o pacote de teste em Go

111

Como posso fazer o processamento geral da configuração do teste que define o cenário para todos os testes ao usar o pacote de teste ?

Como exemplo, em Nunit, há um [SetUp]atributo.

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}
Miltonb
fonte
2
Começando com 1.4, você pode ter configuração global e desmontagem
Salvador Dali

Respostas:

158

A partir do Go 1.4, você pode implementar configuração / desmontagem (não há necessidade de copiar suas funções antes / depois de cada teste). A documentação é descrita aqui na seção Principal :

TestMain é executado na goroutine principal e pode fazer qualquer configuração e desmontagem necessária em torno de uma chamada para m.Run. Ele deve então chamar os.Exit com o resultado de m.Run

Levei algum tempo para descobrir que isso significa que se um teste contiver uma função func TestMain(m *testing.M), essa função será chamada em vez de executar o teste. E nesta função posso definir como os testes serão executados. Por exemplo, posso implementar uma configuração global e desmontagem:

func TestMain(m *testing.M) {
    setup()
    code := m.Run() 
    shutdown()
    os.Exit(code)
}

Alguns outros exemplos podem ser encontrados aqui .

O recurso TestMain adicionado à estrutura de teste de Go na versão mais recente é uma solução simples para vários casos de uso de teste. TestMain fornece um gancho global para realizar a configuração e desligamento, controlar o ambiente de teste, executar código diferente em um processo filho ou verificar se há recursos vazados pelo código de teste. A maioria dos pacotes não precisará de um TestMain, mas é uma adição bem-vinda quando for necessário.

Salvador Dalí
fonte
17
TestMainé uma vez em um pacote, então não é tão útil. Acho que os subtestes são melhores para propósitos mais complexos.
Inanc Gumus de
3
Como você deve passar o contexto da função de configuração para os testes sem usar variáveis ​​globais? Por exemplo, se mySetupFunction () cria um diretório temporário para realizar o teste (com um nome único e aleatório), como os testes sabem o nome do diretório? Deve haver um local para definir este contexto ??
Lqueryvg
1
Parece que esta é a maneira oficial de lidar com os ganchos antes e depois dos testes, consulte golang.org/pkg/testing/#hdr-Main para obter a documentação oficial
de-jcup
4
@InancGumuslstat $GOROOT/subtests: no such file or directory
030
1
observe que 'code: = m.Run ()' é o que executa outros TestFunctions!
Alex Punnen
49

Isso pode ser conseguido colocando um init() função no _test.goarquivo. Isso será executado antes da init()função.

// package_test.go
package main

func init() {
     /* load test data */
}

O _test.init () será chamado antes da função init () do pacote.

Miltonb
fonte
2
Sei que você está respondendo à sua própria pergunta, então isso provavelmente satisfaz seu próprio caso de uso, mas não é equivalente ao exemplo NUnit que você incluiu em sua pergunta.
James Henstridge
Bem, @james, mostrei uma maneira de responder ao problema e outros já forneceram alguns bons insights, incluindo o seu. É útil obter influências externas para ajustar a abordagem. Obrigado.
miltonb
2
Justo. O que você mostrou nesta resposta está um pouco mais próximo de usar o [TestFixtureSetUp]atributo do NUnit .
James Henstridge
2
não inclui a parte de desmontagem
Taras Matsyk
7
Esta não é uma boa solução se o seu arquivo de teste estiver no mesmo pacote com a função principal.
Mouse desejado em
28

Dada uma função simples para teste de unidade:

package math

func Sum(a, b int) int {
    return a + b
}

Você pode testá-lo com uma função de configuração que retorna a função de desmontagem. E depois de chamar setup (), você pode fazer uma chamada adiada para teardown ().

package math

import "testing"

func setupTestCase(t *testing.T) func(t *testing.T) {
    t.Log("setup test case")
    return func(t *testing.T) {
        t.Log("teardown test case")
    }
}

func setupSubTest(t *testing.T) func(t *testing.T) {
    t.Log("setup sub test")
    return func(t *testing.T) {
        t.Log("teardown sub test")
    }
}

func TestAddition(t *testing.T) {
    cases := []struct {
        name     string
        a        int
        b        int
        expected int
    }{
        {"add", 2, 2, 4},
        {"minus", 0, -2, -2},
        {"zero", 0, 0, 0},
    }

    teardownTestCase := setupTestCase(t)
    defer teardownTestCase(t)

    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            teardownSubTest := setupSubTest(t)
            defer teardownSubTest(t)

            result := Sum(tc.a, tc.b)
            if result != tc.expected {
                t.Fatalf("expected sum %v, but got %v", tc.expected, result)
            }
        })
    }
}

A ferramenta de teste Go relatará as declarações de registro no console do shell:

% go test -v
=== RUN   TestAddition
=== RUN   TestAddition/add
=== RUN   TestAddition/minus
=== RUN   TestAddition/zero
--- PASS: TestAddition (0.00s)
    math_test.go:6: setup test case
    --- PASS: TestAddition/add (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/minus (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/zero (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    math_test.go:8: teardown test case
PASS
ok      github.com/kare/go-unit-test-setup-teardown 0.010s
% 

Você pode passar alguns parâmetros adicionais para configurar / desmontar com esta abordagem.

Kare Nuorteva
fonte
2
Bem, esse é um truque realmente simples, mas eficaz. Excelente uso da sintaxe Go.
miltonb
1
Sim, mas aumenta o aninhamento (uma espécie de pirâmide da desgraça em javascript ). E, os testes não são executados automaticamente pelo conjunto como nos testes externos.
Inanc Gumus de
12

Normalmente, os testes em go não são escritos no mesmo estilo que outras linguagens. Freqüentemente, há relativamente menos funções de teste, mas cada uma contém um conjunto de casos de teste baseado em tabela. Vejo este artigo escrito por um membro da equipe Go.

Com um teste baseado em tabela, você simplesmente coloca qualquer código de configuração antes do loop que executa os casos de teste individuais especificados na tabela e coloca qualquer código de limpeza posteriormente.

Se você ainda tiver código de configuração compartilhado entre funções de teste, pode extrair o código de configuração compartilhado em uma função e usar um sync.Oncese for importante que seja executado exatamente uma vez (ou como outra resposta sugere, use init(), mas isso tem a desvantagem de que a configuração será feito mesmo se os casos de teste não forem executados (talvez porque você limitou os casos de teste usando go test -run <regexp>.)

Eu diria que se você acha que precisa de configuração compartilhada entre diferentes testes que são executados exatamente uma vez, você deveria pensar se realmente precisa disso e se um teste baseado em tabela não seria melhor.

Paul Hankin
fonte
6
Isso é ótimo ao testar coisas triviais como um analisador de sinalizador ou um algoritmo que gira em torno de números. Mas isso realmente não ajuda ao tentar testar diversas partes da funcionalidade que requerem código clichê semelhante. Suponho que eu poderia definir minhas funções de teste em uma matriz e iterar sobre elas, mas não é realmente orientado por tabela, e sim um loop simples que deveria ser construído na própria estrutura de teste (na forma de um conjunto de testes adequado com funções de configuração / desmontagem)
iamtheddrman
9

A estrutura de teste Go não tem nada equivalente ao atributo SetUp do NUnit (marcando uma função a ser chamada antes de cada teste no conjunto). Existem algumas opções, porém:

  1. Basta chamar sua SetUpfunção de cada teste onde for necessário.

  2. Use uma extensão para a estrutura de teste do Go que implementa paradigmas e conceitos xUnit. Três opções fortes vêm à mente:

Cada uma dessas bibliotecas encoraja você a organizar seus testes em suítes / fixtures semelhantes a outros frameworks xUnit, e irá chamar os métodos de configuração no tipo de suíte / fixture antes de cada um dos Test*métodos.

James Henstridge
fonte
0

Plug desavergonhado, criei https://github.com/houqp/gtest para ajudar a resolver exatamente esse problema.

Aqui está um exemplo rápido:

import (
  "strings"
  "testing"
  "github.com/houqp/gtest"
)

type SampleTests struct{}

// Setup and Teardown are invoked per test group run
func (s *SampleTests) Setup(t *testing.T)      {}
func (s *SampleTests) Teardown(t *testing.T)   {}
// BeforeEach and AfterEach are invoked per test run
func (s *SampleTests) BeforeEach(t *testing.T) {}
func (s *SampleTests) AfterEach(t *testing.T)  {}

func (s *SampleTests) SubTestCompare(t *testing.T) {
  if 1 != 1 {
    t.FailNow()
  }
}

func (s *SampleTests) SubTestCheckPrefix(t *testing.T) {
  if !strings.HasPrefix("abc", "ab") {
    t.FailNow()
  }
}

func TestSampleTests(t *testing.T) {
  gtest.RunSubTests(t, &SampleTests{})
}

Você pode criar qualquer grupo de teste que desejar dentro de um pacote com cada um deles usando um conjunto diferente de rotinas de configuração / desmontagem.

houqp
fonte