---
title: "Go Testing: TDD e Integração Contínua - Guia Completo"
url: "https://golang.com.br/tutoriais/go-tdd-ci-cd/"
markdown_url: "https://golang.com.br/tutoriais/go-tdd-ci-cd.MD"
description: "Aprenda Test-Driven Development (TDD) e CI/CD para Go. Tutorial completo com GitHub Actions, testes automatizados, pipelines e deploy contínuo para projetos Go profissionais."
date: "2026-02-11"
author: ""
---

# Go Testing: TDD e Integração Contínua - Guia Completo

Aprenda Test-Driven Development (TDD) e CI/CD para Go. Tutorial completo com GitHub Actions, testes automatizados, pipelines e deploy contínuo para projetos Go profissionais.


Test-Driven Development (TDD) e Integração Contínua (CI/CD) são práticas essenciais para desenvolvimento de software profissional. Este guia completo mostra como implementar TDD e pipelines de CI/CD em projetos Go, desde testes unitários até deploy automatizado.

## Por Que TDD + CI/CD em Go?

Go é ideal para TDD e CI/CD porque:

- **Testes rápidos** — Compilação e execução em segundos
- **Binário único** — Deploy simplificado
- **Biblioteca padrão robusta** — Sem dependências complexas
- **Cross-compilation nativa** — Build para múltiplas plataformas

## Fundamentos de TDD

### O Ciclo Red-Green-Refactor

```
┌─────────┐    ┌─────────┐    ┌─────────┐
│   RED   │ →  │  GREEN  │ →  │ REFACTOR│
│  (Falha)│    │ (Passa) │    │(Melhora)│
└─────────┘    └─────────┘    └─────────┘
     ↑                              │
     └──────────────────────────────┘
```

### Exemplo Prático: Calculadora com TDD

**Passo 1: Escreva o teste (RED)**

```go
// calculator_test.go
package calculator

import "testing"

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

Execute: `go test` — **FALHA** (código não existe)

**Passo 2: Implementação mínima (GREEN)**

```go
// calculator.go
package calculator

type Calculator struct{}

func NewCalculator() *Calculator {
    return &Calculator{}
}

func (c *Calculator) Add(a, b int) int {
    return a + b
}
```

Execute: `go test` — **PASSA** ✓

**Passo 3: Refatoração**

```go
// Adicione mais casos de teste
func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positivos", 2, 3, 5},
        {"negativos", -2, -3, -5},
        {"zero", 0, 5, 5},
        {"ambos_zero", 0, 0, 0},
    }

    calc := NewCalculator()
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := calc.Add(tt.a, tt.b)
            if got != tt.expected {
                t.Errorf("Add(%d, %d) = %d; esperado %d",
                    tt.a, tt.b, got, tt.expected)
            }
        })
    }
}
```

## Código Testável em Go

### Princípios SOLID para Testes

**1. Inversão de Dependência (DIP)**

```go
// ❌ Ruim: Acoplamento direto
type Service struct {
    db *sql.DB // dependência concreta
}

// ✅ Bom: Dependa de interfaces
type Repository interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

type Service struct {
    repo Repository // dependência abstrata
}

func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}
```

**2. Injeção de Dependências**

```go
// main.go - Wiring manual
func main() {
    db, _ := sql.Open("postgres", dsn)
    repo := NewPostgresRepository(db)
    service := NewService(repo)
    handler := NewHandler(service)
    // ...
}
```

## Mocking e Stubs

### Mock Manual

```go
// Mock para testes
type MockRepository struct {
    users map[string]*User
    err   error
}

func (m *MockRepository) GetUser(id string) (*User, error) {
    if m.err != nil {
        return nil, m.err
    }
    return m.users[id], nil
}

func (m *MockRepository) SaveUser(user *User) error {
    if m.err != nil {
        return m.err
    }
    m.users[user.ID] = user
    return nil
}
```

### Teste com Mock

```go
func TestService_GetUser(t *testing.T) {
    // Setup
    mockRepo := &MockRepository{
        users: map[string]*User{
            "123": {ID: "123", Name: "João"},
        },
    }
    svc := NewService(mockRepo)

    // Execute
    user, err := svc.GetUser("123")

    // Assert
    if err != nil {
        t.Errorf("erro inesperado: %v", err)
    }
    if user.Name != "João" {
        t.Errorf("nome = %s; esperado João", user.Name)
    }
}

func TestService_GetUser_NotFound(t *testing.T) {
    mockRepo := &MockRepository{users: map[string]*User{}}
    svc := NewService(mockRepo)

    _, err := svc.GetUser("999")
    
    if err != ErrUserNotFound {
        t.Errorf("erro = %v; esperado ErrUserNotFound", err)
    }
}
```

## Análise de Cobertura

### Gerar Relatório de Coverage

```bash
# Cobertura do pacote atual
go test -cover ./...

# Cobertura detalhada
go test -coverprofile=coverage.out ./...

# Visualizar em HTML
go tool cover -html=coverage.out -o coverage.html

# Ver funções não cobertas
go tool cover -func=coverage.out
```

### Cobertura Mínima em CI

```bash
#!/bin/bash
# check-coverage.sh

THRESHOLD=80
coverage=$(go test -cover ./... | grep -o '[0-9.]*%' | tr -d '%' | awk '{s+=$1; n++} END {printf "%.2f", s/n}')

echo "Cobertura: $coverage%"

if (( $(echo "$coverage < $THRESHOLD" | bc -l) )); then
    echo "❌ Cobertura abaixo de $THRESHOLD%"
    exit 1
fi

echo "✅ Cobertura aceita"
```

## GitHub Actions para Go

### Pipeline Básica

```yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'
          cache: true
      
      - name: Download dependencies
        run: go mod download
      
      - name: Run tests
        run: go test -v -race ./...
      
      - name: Check coverage
        run: |
          go test -coverprofile=coverage.out ./...
          go tool cover -func=coverage.out
```

### Pipeline Avançada

```yaml
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  GO_VERSION: '1.22'
  REGISTRY: ghcr.io

jobs:
  # Job 1: Lint e Testes
  test:
    name: Testes
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}

      - name: Cache Go modules
        uses: actions/cache@v4
        with:
          path: ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}

      - name: Download dependencies
        run: go mod download

      - name: Run linter
        uses: golangci/golangci-lint-action@v3
        with:
          version: latest
          args: --timeout=5m

      - name: Run tests
        run: go test -v -race -coverprofile=coverage.out ./...

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.out
          fail_ci_if_error: false

  # Job 2: Build
  build:
    name: Build
    runs-on: ubuntu-latest
    needs: test
    
    strategy:
      matrix:
        os: [linux, darwin, windows]
        arch: [amd64, arm64]
        exclude:
          - os: windows
            arch: arm64

    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}

      - name: Build binary
        env:
          GOOS: ${{ matrix.os }}
          GOARCH: ${{ matrix.arch }}
        run: |
          output_name="myapp-${{ matrix.os }}-${{ matrix.arch }}"
          if [ "$GOOS" = "windows" ]; then
            output_name+='.exe'
          fi
          go build -ldflags="-s -w" -o "dist/$output_name" ./cmd/app

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: binaries
          path: dist/

  # Job 3: Deploy (somente na main)
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: [test, build]
    if: github.ref == 'refs/heads/main'
    
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: binaries
          path: dist/

      - name: Deploy to staging
        run: |
          echo "Deploy para staging..."
          # Comandos de deploy aqui
```

## Benchmarks em CI

### Detectar Regressões de Performance

```yaml
      - name: Run benchmarks
        run: |
          go test -bench=. -benchmem ./... | tee benchmark.txt
      
      - name: Compare benchmarks
        uses: benchmark-action/github-action-benchmark@v1
        with:
          tool: 'go'
          output-file-path: benchmark.txt
          github-token: ${{ secrets.GITHUB_TOKEN }}
          auto-push: true
```

### Benchmark Script

```go
// Exemplo de benchmark para CI
func BenchmarkProcessData(b *testing.B) {
    data := generateTestData(1000)
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        ProcessData(data)
    }
}

func BenchmarkProcessDataParallel(b *testing.B) {
    data := generateTestData(1000)
    
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            ProcessData(data)
        }
    })
}
```

## Testes de Integração

### Estratégia de Testes

```
unit/
├── service_test.go      # Testes unitários (mock)
└── handler_test.go      # Testes HTTP (httptest)

integration/
├── database_test.go     # Testes com banco real
└── api_test.go          # Testes end-to-end
```

### Test Container para PostgreSQL

```go
// integration_test.go
//go:build integration

package integration

import (
    "context"
    "testing"
    
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/postgres"
)

func TestDatabaseIntegration(t *testing.T) {
    ctx := context.Background()
    
    // Criar container PostgreSQL
    container, err := postgres.Run(ctx,
        "postgres:16",
        postgres.WithDatabase("testdb"),
        postgres.WithUsername("test"),
        postgres.WithPassword("test"),
    )
    if err != nil {
        t.Fatal(err)
    }
    defer container.Terminate(ctx)
    
    // Obter connection string
    connStr, _ := container.ConnectionString(ctx)
    
    // Testar com banco real
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        t.Fatal(err)
    }
    
    // Executar testes...
}
```

Execute: `go test -tags=integration ./integration/`

## Projeto Real: Estrutura Completa

```
myproject/
├── .github/
│   └── workflows/
│       ├── ci.yml          # Testes e lint
│       └── release.yml     # Build e release
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── domain/
│   │   ├── user.go
│   │   └── user_test.go
│   ├── service/
│   │   ├── user_service.go
│   │   └── user_service_test.go
│   └── repository/
│       ├── postgres/
│       │   ├── user_repo.go
│       │   └── user_repo_test.go
│       └── mock/
│           └── user_repo_mock.go
├── pkg/
│   └── utils/
│       └── validator.go
├── Makefile
├── go.mod
└── README.md
```

### Makefile Útil

```makefile
.PHONY: test coverage lint build clean

test:
	go test -v -race ./...

coverage:
	go test -coverprofile=coverage.out ./...
	go tool cover -html=coverage.out

lint:
	golangci-lint run

build:
	go build -ldflags="-s -w" -o bin/api ./cmd/api

clean:
	rm -rf bin/ coverage.out

integration-test:
	go test -tags=integration ./integration/...

dev:
	air
```

## Checklist TDD + CI/CD

### Antes de Commitar

- [ ] Todos os testes passam (`go test ./...`)
- [ ] Cobertura > 80%
- [ ] Linter sem erros (`golangci-lint run`)
- [ ] Código formatado (`go fmt ./...`)
- [ ] Módulos atualizados (`go mod tidy`)

### Pipeline CI/CD

- [ ] Build em múltiplas plataformas
- [ ] Testes unitários
- [ ] Testes de integração
- [ ] Análise de segurança (`govulncheck`)
- [ ] Verificação de cobertura
- [ ] Deploy automatizado

## Métricas de Qualidade

### Dashboard de Métricas

| Métrica | Meta | Ferramenta |
|---------|------|------------|
| Cobertura | > 80% | `go test -cover` |
| Lint | 0 erros | golangci-lint |
| Vulnerabilidades | 0 críticas | govulncheck |
| Build Time | < 5 min | GitHub Actions |
| Test Duration | < 2 min | go test |

## Próximos Passos

- [Go Clean Architecture](/tutoriais/go-clean-architecture/) — Estrutura testável
- [Go Performance Profiling](/tutoriais/go-performance-profiling/) — Otimização com benchmarks
- [GitHub Actions Docs](https://docs.github.com/actions) — Automação avançada

---

*TDD e CI/CD garantem código confiável e entrega contínua. Implemente hoje!*
