0Pricing
DevOps Bootcamp · Aula

Teste unitário de funções com Bats-core

Estruture arquivos de teste, asserções e preparação e limpeza para verificar funções individuais do Bash.

Teste unitário de funções com Bats-core é uma aula grátis de DevOps Bootcamp no CoddyKit. Esta é a aula 1 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de DevOps Bootcamp, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de DevOps Bootcamp inclui 4 aulas no total.

O que é o Bats-core e por que usá-lo?

Bats-core (Bash Automated Testing System) é o framework de testes unitários de facto para Bash. Ele permite escrever testes estruturados e reproduzíveis para suas funções e scripts de shell — da mesma forma que você usaria JUnit para Java ou pytest para Python.

  • Cada teste é um bloco @test com uma descrição compreensível.
  • Os testes passam quando cada comando interno retorna o código de saída 0.
  • Os testes falham no primeiro código de saída diferente de zero ou quando uma asserção falha.
  • A saída é compatível com TAP, portanto os sistemas de integração contínua (GitHub Actions, Jenkins, GitLab CI) a entendem nativamente.

Instale usando o gerenciador de pacotes ou clone o repositório:

# Install via git (recommended — always latest)
git clone https://github.com/bats-core/bats-core.git
cd bats-core && sudo ./install.sh /usr/local

# Or on macOS with Homebrew
brew install bats-core

# Verify installation
bats --version
# bats 1.x.y

Seu primeiro arquivo de teste do Bats

Um arquivo de teste do Bats tem a extensão .bats e começa com um shebang especial. O bloco de construção principal é a diretiva @test, seguida por uma cadeia de descrição e um bloco de comandos.

  • O shebang #!/usr/bin/env bats informa ao shell como executar o arquivo.
  • Cada bloco @test é um caso de teste independente.
  • Você pode executar um único arquivo com bats my_tests.bats ou um diretório inteiro com bats test/.

A estrutura mínima de um arquivo de teste do Bats é apresentada abaixo:

#!/usr/bin/env bats
# File: test/hello.bats

@test "echo outputs the expected string" {
  result=$(echo "hello world")
  [ "$result" = "hello world" ]
}

@test "false command causes test to fail" {
  # Uncommenting the next line would make this test fail:
  # false
  true
}

Carregando a função em teste com 'load'

Em projetos reais, suas funções Bash ficam em arquivos de biblioteca, não no próprio arquivo de teste. O Bats fornece o auxiliar load para carregar arquivos externos relativos ao diretório do arquivo de teste.

  • load '../lib/math.sh' carrega o arquivo antes da execução de cada teste.
  • Depois do carregamento, todas as funções definidas nesse arquivo ficam disponíveis nos seus blocos de teste.
  • Mantenha suas funções de biblioteca em um diretório lib/ e os testes em um diretório test/ para obter uma separação clara.

Exemplo de organização do projeto e do teste correspondente:

# Project layout:
# lib/math.sh       <- functions to test
# test/math.bats    <- test file

# lib/math.sh
add() {
  echo $(( $1 + $2 ))
}

divide() {
  if [ "$2" -eq 0 ]; then
    echo "Error: division by zero" >&2
    return 1
  fi
  echo $(( $1 / $2 ))
}

# test/math.bats
#!/usr/bin/env bats

load '../lib/math.sh'

@test "add returns correct sum" {
  result=$(add 3 4)
  [ "$result" = "7" ]
}

Asserções principais: run, $status, $output

O comando run é o núcleo dos testes com Bats. Em vez de executar um comando diretamente, envolvê-lo com run captura seu código de saída e sua saída sem fazer o teste falhar imediatamente.

  • $status — contém o código de saída do último comando run.
  • $output — contém a saída stdout combinada do último comando run.
  • $lines — uma matriz em que cada elemento é uma linha da saída (${lines[0]}, ${lines[1]} etc.).

Isso permite fazer asserções tanto em casos de sucesso quanto de falha:

#!/usr/bin/env bats

load '../lib/math.sh'

@test "divide 10 by 2 returns 5" {
  run divide 10 2
  [ "$status" -eq 0 ]
  [ "$output" = "5" ]
}

@test "divide by zero returns exit code 1" {
  run divide 10 0
  [ "$status" -eq 1 ]
}

@test "divide by zero prints error message" {
  run divide 10 0
  # $output captures stderr too when redirected inside the function
  [[ "$output" == *"division by zero"* ]]
}

Usando bats-assert para asserções mais expressivas

As asserções integradas [ ] funcionam, mas fornecem mensagens de falha pouco úteis. A biblioteca auxiliar bats-assert oferece funções de asserção expressivas que mostram exatamente o que deu errado.

  • assert_success — verifica se $status é 0.
  • assert_failure — verifica se $status é diferente de zero.
  • assert_output — verifica se $output é igual à cadeia fornecida.
  • assert_output --partial — verifica se a saída contém a subcadeia.
  • refute_output --partial — verifica se a saída NÃO contém a subcadeia.

Instale clonando bats-core/bats-assert em uma pasta test/helpers/ e carregue-o:

#!/usr/bin/env bats

# Load bats-assert (cloned into test/helpers/bats-assert)
load 'helpers/bats-assert/load'
load '../lib/math.sh'

@test "add 5 and 3 gives 8" {
  run add 5 3
  assert_success
  assert_output "8"
}

@test "divide by zero fails with descriptive message" {
  run divide 9 0
  assert_failure
  assert_output --partial "division by zero"
}

@test "add does not output an error" {
  run add 1 1
  refute_output --partial "Error"
}

setup e teardown: ganchos do ciclo de vida dos testes

O Bats fornece duas funções especiais — setup e teardown — que são executadas automaticamente ao redor de cada teste. Use-as para preparar e limpar o estado compartilhado, garantindo que cada teste comece em um ambiente conhecido.

  • setup() é executada antes de cada bloco @test individual.
  • teardown() é executada depois de cada bloco @test individual, mesmo que o teste falhe.
  • Usos comuns: criar diretórios temporários, definir variáveis de ambiente e remover arquivos temporários após o teste.
#!/usr/bin/env bats

load '../lib/fileutils.sh'

setup() {
  # Create a fresh temp directory before every test
  TEST_DIR=$(mktemp -d)
  export TEST_DIR
}

teardown() {
  # Always clean up, even on test failure
  rm -rf "$TEST_DIR"
}

@test "write_file creates a file with correct content" {
  run write_file "$TEST_DIR/hello.txt" "hello world"
  assert_success
  [ -f "$TEST_DIR/hello.txt" ]
  [ "$(cat "$TEST_DIR/hello.txt")" = "hello world" ]
}

@test "write_file fails when directory does not exist" {
  run write_file "/nonexistent/dir/file.txt" "data"
  assert_failure
}

setup_file e teardown_file: ganchos no nível do conjunto de testes

Às vezes, você só precisa configurar recursos dispendiosos uma vez por arquivo — não antes de cada teste. O Bats fornece setup_file e teardown_file para essa finalidade.

  • setup_file() é executada uma vez antes de todos os testes do arquivo.
  • teardown_file() é executada uma vez depois de todos os testes do arquivo.
  • Use BATS_FILE_TMPDIR (disponível automaticamente) para compartilhar dados entre setup_file e seus testes — variáveis comuns não persistem entre subprocessos.

Caso de uso típico: iniciar um servidor simulado ou compilar um binário uma vez e encerrá-lo ao final:

#!/usr/bin/env bats

setup_file() {
  # Build the project binary once for all tests in this file
  make build --silent
  export BINARY="$PWD/bin/myapp"
  echo "Binary built: $BINARY"
}

teardown_file() {
  # Remove the binary after all tests complete
  rm -f "$BINARY"
  echo "Cleaned up binary"
}

setup() {
  # Still runs before each individual test
  TEST_TMP=$(mktemp -d)
}

teardown() {
  rm -rf "$TEST_TMP"
}

@test "myapp --version outputs version string" {
  run "$BINARY" --version
  assert_output --partial "1.0"
}

Testando funções que modificam arquivos

Um padrão muito comum é testar funções Bash que leem ou gravam no sistema de arquivos. A técnica principal é usar diretórios temporários (por meio de mktemp -d em setup) para que os testes nunca toquem em arquivos reais nem interfiram uns nos outros.

  • Trabalhe sempre dentro de $TEST_DIR (ou $BATS_TEST_TMPDIR — disponível automaticamente nas versões recentes do Bats).
  • Use a biblioteca auxiliar bats-file para asserções claras sobre arquivos, como assert_file_exists e assert_file_contains.
  • Nunca fixe caminhos como /tmp/myfile diretamente no código — execuções paralelas dos testes entrarão em conflito.
#!/usr/bin/env bats

load 'helpers/bats-assert/load'
load 'helpers/bats-file/load'
load '../lib/fileutils.sh'

setup() {
  TEST_DIR="$BATS_TEST_TMPDIR"
}

# lib/fileutils.sh defines:
# append_line() { echo "$2" >> "$1"; }

@test "append_line adds a line to an existing file" {
  echo "first line" > "$TEST_DIR/log.txt"

  run append_line "$TEST_DIR/log.txt" "second line"
  assert_success

  assert_file_contains "$TEST_DIR/log.txt" "second line"
}

@test "append_line creates file if it does not exist" {
  run append_line "$TEST_DIR/new.txt" "hello"
  assert_success
  assert_file_exists "$TEST_DIR/new.txt"
}

Simulando comandos externos

As funções frequentemente chamam programas externos, como curl, aws ou git. Em testes unitários, você quer testar a sua lógica, não o comando externo real. A técnica de simulação mais simples no Bats é definir uma função de shell com o mesmo nome do comando dentro de setup — ela terá precedência sobre o binário real.

  • Defina uma função como curl() { echo 'mocked response'; return 0; } em setup e exporte-a.
  • Use export -f curl para que a função fique visível nos subprocessos iniciados por run.
  • Você também pode gravar a simulação em um arquivo temporário no PATH para cenários mais complexos.
#!/usr/bin/env bats

load 'helpers/bats-assert/load'
load '../lib/network.sh'

# lib/network.sh defines:
# fetch_status() {
#   local url="$1"
#   local code
#   code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
#   echo "$code"
# }

setup() {
  # Override 'curl' with a mock function
  curl() {
    # Simulate a 200 OK response
    echo "200"
    return 0
  }
  export -f curl
}

@test "fetch_status returns 200 when curl reports 200" {
  run fetch_status "https://example.com"
  assert_success
  assert_output "200"
}

Ignorando testes e adicionando etiquetas

Nem todo teste pode ser executado sempre — às vezes você precisa de uma conexão de rede real, de uma ferramenta específica ou de um determinado OS. O Bats fornece skip para ignorar condicionalmente um teste com uma mensagem informativa, em vez de comentá-lo ou interromper o conjunto de testes.

  • Chame skip "reason" em qualquer lugar dentro de um bloco @test para ignorar esse teste.
  • Os testes ignorados aparecem na saída como S e não são contabilizados como falhas.
  • O Bats 1.5 ou posterior oferece suporte a etiquetas: anote os testes com # bats test_tags=slow,network e filtre-os com bats --filter-tags network test/.
#!/usr/bin/env bats

# bats test_tags=network
@test "API returns valid JSON" {
  # Skip if no internet connectivity
  if ! ping -c1 -W1 8.8.8.8 &>/dev/null; then
    skip "No network connection available"
  fi

  run curl -s "https://api.example.com/health"
  assert_success
  assert_output --partial '"status"'
}

# bats test_tags=unit
@test "slug function lowercases and replaces spaces" {
  # Always runs — pure function, no external deps
  slug() { echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-'; }
  run slug "Hello World"
  assert_output "hello-world"
}

# Run only unit tests:
# bats --filter-tags unit test/

Estruturando um conjunto de testes completo

Um projeto Bats bem organizado segue uma estrutura de diretórios previsível, facilitando a integração de novos colaboradores e a integração com pipelines de integração contínua.

Estrutura recomendada:

  • lib/ — funções Bash de produção (um arquivo por responsabilidade: math.sh, fileutils.sh).
  • test/ — um arquivo .bats por arquivo de biblioteca (math.bats, fileutils.bats).
  • test/helpers/ — bats-assert, bats-file e bats-support como submódulos do git.
  • Makefile — um destino test para que os colaboradores simplesmente executem make test.

Execute o conjunto completo de testes com um único comando:

# Makefile
.PHONY: test
test:
	bats test/

# Run all tests recursively (Bats 1.5+)
# bats --recursive test/

# Run a specific file
# bats test/math.bats

# Run with verbose (TAP) output for CI
# bats --tap test/

# Example directory tree:
# .
# |-- lib/
# |   |-- math.sh
# |   `-- fileutils.sh
# |-- test/
# |   |-- helpers/
# |   |   |-- bats-assert/
# |   |   `-- bats-file/
# |   |-- math.bats
# |   `-- fileutils.bats
# `-- Makefile

Verificação de conhecimentos: asserções do Bats-core

Verifique sua compreensão do mecanismo central de testes do Bats-core.

Recapitulação: testes unitários de Bash com Bats-core

Nesta lição, você aprendeu a estruturar e escrever testes unitários para funções Bash individuais usando o Bats-core. Veja os principais pontos:

  • Estrutura do arquivo de teste — use o shebang #!/usr/bin/env bats e blocos @test com nomes descritivos.
  • load — carregue seus arquivos de biblioteca para que as funções fiquem disponíveis nos testes sem copiar e colar.
  • run + $status + $output — o trio principal; use sempre run para capturar os resultados sem fazer o teste falhar imediatamente.
  • bats-assert — prefira assert_success, assert_failure e assert_output a [ ] puro para obter mensagens de falha legíveis.
  • setup / teardown — executados antes/depois de cada teste; setup_file / teardown_file são executados uma vez por arquivo.
  • Simulação — substitua comandos externos por funções de shell com o mesmo nome, exportadas com export -f.
  • skip — ignore condicionalmente testes que dependem de recursos indisponíveis.
  • Organização do projeto — mantenha lib/, test/ e test/helpers/ separados para facilitar a manutenção e a integração contínua.

Esses padrões dão aos seus projetos Bash a mesma disciplina de testes que você aplicaria a qualquer projeto de software moderno.

Perguntas Frequentes

A aula “Teste unitário de funções com Bats-core” é grátis?

Sim — o texto completo de “Teste unitário de funções com Bats-core” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de DevOps Bootcamp, atualize para CoddyKit PRO. O curso de DevOps Bootcamp inclui 4 aulas no total.

O que vou aprender em “Teste unitário de funções com Bats-core”?

Estruture arquivos de teste, asserções e preparação e limpeza para verificar funções individuais do Bash. Você pratica DevOps Bootcamp com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar DevOps Bootcamp?

Nenhuma experiência prévia é necessária. DevOps Bootcamp no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 1 de 4.

Quanto tempo leva a aula “Teste unitário de funções com Bats-core”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de DevOps Bootcamp?

Sim. Cada aula de DevOps Bootcamp inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Teste unitário de funções com Bats-core
  2. Simulação de comandos e criação de substitutos para ferramentas externas
  3. Dispositivos de teste, ambientes temporários e cobertura
  4. Execução de testes de Shell em pipelines de integração contínua
← Voltar para DevOps Bootcamp