Testes em Go 2026: Testes Unitários, Mocks e Perguntas de Entrevista Técnica

Guia completo sobre testes em Go: escrita de testes unitários com o pacote testing, criação de mocks com gomock, testes baseados em tabelas e preparação para entrevistas técnicas.

Testes unitários e mocks em Go

O framework de testes do Go se baseia no pacote padrão testing, incluído em toda instalação do Go sem dependências externas. Diferente de frameworks em outras linguagens, a abordagem do Go privilegia a simplicidade: os arquivos de teste convivem com o código de produção, as funções de teste seguem uma convenção de nomenclatura, e o comando go test gerencia a descoberta e execução.

O que os entrevistadores procuram

Candidatos que escrevem testes baseados em tabelas, utilizam interfaces para injeção de dependências e compreendem quando o mocking agrega valor versus quando adiciona ruído.

Escrevendo Testes Unitários com o Pacote testing

Cada arquivo de teste termina com _test.go e pertence ao mesmo pacote que o código testado. As funções de teste começam com Test seguido de um nome em maiúscula. O parâmetro *testing.T fornece métodos para reportar falhas.

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

A execução de go test ./... roda todos os testes do módulo atual. A flag -v exibe os nomes dos testes individuais e seus status.

Testes Baseados em Tabelas: O Padrão Go

Os testes baseados em tabelas reduzem duplicação e tornam trivial adicionar novos casos. Cada linha da tabela representa um cenário com suas entradas e saídas esperadas. Esse padrão aparece na biblioteca padrão do Go e em bases de código em produção.

validator_test.gogo
package validator

import "testing"

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {"valid email", "user@example.com", false},
        {"missing at sign", "userexample.com", true},
        {"empty string", "", true},
        {"unicode local part", "user@例え.jp", false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
                    tt.email, err, tt.wantErr)
            }
        })
    }
}

O método t.Run cria subtestes que rodam independentemente. Subtestes que falham indicam qual caso específico falhou sem interromper os demais casos.

Injeção de Dependências por meio de Interfaces

As interfaces do Go permitem testar código que depende de sistemas externos: bancos de dados, clientes HTTP, sistemas de arquivos. Definir uma interface mínima no ponto de uso permite substituir implementações reais por doubles de teste.

service.gogo
package order

// Repository defines the data access methods this service needs
type Repository interface {
    FindByID(id string) (*Order, error)
    Save(order *Order) error
}

// Service handles order business logic
type Service struct {
    repo Repository
}

// NewService creates a service with the given repository
func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}

// Process validates and saves an order
func (s *Service) Process(order *Order) error {
    if order.Total <= 0 {
        return ErrInvalidTotal
    }
    return s.repo.Save(order)
}

O Service aceita qualquer tipo que satisfaça Repository. O código de produção passa uma implementação conectada ao banco de dados. Os testes passam um mock ou stub.

Mocking com gomock e mockgen

O pacote gomock gera implementações mock a partir de definições de interface. O comando mockgen lê arquivos fonte e produz structs mock que registram chamadas e retornam valores configurados.

bash
# Generate mocks for the Repository interface
mockgen -source=service.go -destination=mocks/repository_mock.go -package=mocks

Usando o mock gerado nos testes:

service_test.gogo
package order

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "go.uber.org/mock/gomock"

    "yourmodule/order/mocks"
)

func TestService_Process_SavesValidOrder(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    order := &Order{ID: "123", Total: 99.99}

    // Expect Save to be called once with this order, return nil
    mockRepo.EXPECT().
        Save(order).
        Return(nil).
        Times(1)

    svc := NewService(mockRepo)
    err := svc.Process(order)

    assert.NoError(t, err)
}

func TestService_Process_RejectsZeroTotal(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    // No calls expected to repo since validation fails first

    svc := NewService(mockRepo)
    err := svc.Process(&Order{ID: "456", Total: 0})

    assert.ErrorIs(t, err, ErrInvalidTotal)
}

O mock verifica que Save foi chamado exatamente uma vez com o argumento esperado. Se o código sob teste nunca chama Save, ou o chama com argumentos diferentes, o teste falha.

Usando testify para Assertions e Suites

testify fornece funções de assertion que produzem mensagens de falha mais claras que verificações manuais com if. O pacote assert continua a execução após falhas. O pacote require interrompe o teste imediatamente.

user_test.gogo
package user

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestParseUser(t *testing.T) {
    input := `{"id": "u1", "name": "Alice"}`

    user, err := ParseUser(input)

    require.NoError(t, err, "parsing should not fail")
    assert.Equal(t, "u1", user.ID)
    assert.Equal(t, "Alice", user.Name)
    assert.Empty(t, user.Email, "email should default to empty")
}

A chamada require.NoError interrompe o teste se o parsing falhar, evitando panics de ponteiro nil nas assertions seguintes.

Pronto para mandar bem nas entrevistas de Go?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Testando Handlers HTTP com httptest

O pacote net/http/httptest fornece ResponseRecorder para capturar a saída de handlers e Server para rodar um servidor de teste local. A maioria dos testes unitários usa ResponseRecorder para evitar overhead de rede.

handler_test.gogo
package api

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestHealthHandler(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    rec := httptest.NewRecorder()

    HealthHandler(rec, req)

    assert.Equal(t, http.StatusOK, rec.Code)
    assert.JSONEq(t, `{"status": "ok"}`, rec.Body.String())
}

Para handlers que chamam serviços externos, convém injetar um cliente mock ou usar httptest.NewServer para criar um backend fictício.

Perguntas de Entrevista Comuns sobre Testes em Go

Os entrevistadores avaliam o conhecimento de testes através de perguntas que revelam experiência com bases de código reais. Estes são os padrões que distinguem candidatos preparados:

«Como você testaria uma função que chama uma API externa?»

Deve-se definir uma interface para o cliente HTTP, injetá-la na função ou struct, e fornecer um mock que retorne respostas predeterminadas. Isso evita chamadas de rede e torna os testes determinísticos.

«Quando você usaria um stub versus um mock?»

Stubs retornam respostas predefinidas sem verificar como foram chamados. Mocks verificam que métodos específicos foram chamados com argumentos específicos. Stubs são preferíveis quando o valor de retorno importa mais que a interação. Mocks convêm quando verificar a interação é o objetivo do teste.

«O que são testes baseados em tabelas e por que o Go os privilegia?»

Testes baseados em tabelas definem casos de teste como dados em um slice de structs. Eles reduzem código repetitivo, tornam trivial adicionar casos e produzem saída de teste legível com t.Run. Esse padrão se alinha com a preferência do Go por código explícito e repetitivo em vez de abstrações complexas.

«Como você gerencia fixtures de teste ou setup compartilhado por múltiplos testes?»

Recomenda-se usar TestMain para setup e teardown em nível de pacote, e funções helper ou suites de testes (via testify) para setup compartilhado entre testes relacionados. Estado global que cria acoplamento entre testes deve ser evitado.

Para se preparar para uma entrevista de Go, o módulo de testes Go cobre esses padrões em profundidade com perguntas práticas.

Executando Testes com Cobertura e Detecção de Race Conditions

O comando go test aceita flags que revelam caminhos de código não testados e bugs de concorrência.

bash
# Run tests with coverage report
go test -cover ./...

# Generate HTML coverage visualization
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html

# Detect race conditions (slower, but catches real bugs)
go test -race ./...

O detector de race instrumenta acessos à memória e sinaliza leituras e escritas concorrentes na mesma variável. Rodar testes com -race em CI permite detectar bugs que só se manifestam sob condições de timing específicas.

Organizando Testes em Bases de Código Grandes

À medida que os projetos crescem, a organização dos testes afeta a manutenibilidade:

  • Testes do mesmo pacote (package foo) acessam funções e campos não exportados. São usados para testes unitários que verificam comportamento interno.
  • Testes de pacote externo (package foo_test) importam o pacote como código externo. São usados para testes de integração e para verificar a API pública.
  • Diretórios testdata armazenam fixtures como arquivos JSON, saídas de referência ou bancos de dados de seed. O Go os ignora durante os builds.
  • Build tags separam testes unitários de testes de integração que requerem sistemas externos. Executar go test -tags=integration ./... para incluí-los.

Para mais informações sobre boas práticas de Go além dos testes, o artigo sobre padrões de concorrência aborda goroutines e channels.

Pontos-Chave sobre Testes em Go

  • O pacote testing vem incluído com o Go e não requer dependências externas
  • Testes baseados em tabelas reduzem duplicação e são o padrão esperado em entrevistas
  • Interfaces permitem injeção de dependências; devem ser definidas no ponto de uso, não na implementação
  • gomock gera mocks a partir de interfaces; testify fornece assertions mais claras
  • httptest.ResponseRecorder captura saída de handlers sem overhead de rede
  • A flag -race detecta bugs de concorrência que testes unitários de outra forma não detectam
  • Métricas de cobertura guiam a atenção, mas não garantem correção

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Desafio do dia

Você saberia encontrar o bug em Go?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 24 de agosto de 2026

Tags

#go
#testing
#unit-tests
#mocks
#gomock
#testify

Compartilhar

Artigos relacionados