Testes Automatizados em Ruby: Minitest vs RSpec
Testes automatizados são uma parte fundamental do desenvolvimento Ruby moderno. Eles não apenas garantem que nosso código funcione conforme esperado, mas também nos ajudam a escrever código mais limpo e bem estruturado. Neste tutorial, vamos explorar os dois principais frameworks de teste em Ruby: minitest e RSpec.
Por Que Testes Unitários São Essenciais
Testes unitários focam em pequenos pedaços de código, geralmente métodos individuais ou branches dentro de métodos. Eles oferecem várias vantagens importantes:
Detecção Precoce de Bugs: Quando você testa o código enquanto o escreve, é mais provável encontrar bugs enquanto ainda estão frescos na sua mente. Além disso, quando um bug aparece, você só precisa procurar em algumas linhas de código.
Design Melhor: Pensar sobre testes naturalmente leva você a criar designs melhores e mais desacoplados. O processo de escrever testes expõe fraquezas na estrutura do código.
Testando com Minitest: O Framework Padrão
Minitest é o framework de testes que vem com a biblioteca padrão do Ruby. Vamos começar com um exemplo prático criando uma classe para números romanos:
# Classe para converter números para algarismos romanos
class NumeroRomano
MAX_ROMANO = 4999
def initialize(valor)
if valor <= 0 || valor > MAX_ROMANO
fail "Valores romanos devem ser > 0 e <= #{MAX_ROMANO}"
end
@valor = valor
end
FATORES = [
["m", 1000], ["cm", 900], ["d", 500],
["c", 100], ["x", 10], ["i", 1]
]
def to_s
valor = @valor
romano = ""
FATORES.each do |codigo, fator|
contagem, valor = valor.divmod(fator)
romano << (codigo * contagem)
end
romano
end
end
Criando Testes com Minitest
Agora vamos criar testes para nossa classe. Minitest usa assertions (afirmações) para verificar se o código funciona conforme esperado:
require_relative "numero_romano"
require "minitest/autorun"
class TestNumeroRomano < Minitest::Test
def assert_valor_romano(numeral_romano, numero_arabico)
assert_equal(numeral_romano, NumeroRomano.new(numero_arabico).to_s)
end
def test_conversoes_simples
assert_valor_romano("i", 1)
assert_valor_romano("ii", 2)
assert_valor_romano("iv", 4)
assert_valor_romano("ix", 9)
end
def test_validacao_intervalo
# Sem exceção para valores válidos
NumeroRomano.new(1)
NumeroRomano.new(4999)
# Mas exceção para valores inválidos
assert_raises(RuntimeError) { NumeroRomano.new(0) }
assert_raises(RuntimeError) { NumeroRomano.new(5000) }
end
end
Setup e Teardown em Minitest
Para código de configuração comum, minitest oferece métodos setup e teardown que são executados antes e depois de cada teste:
class TestPlaylistBuilder < Minitest::Test
def setup
@banco_dados = DBI.new("DBI:mysql:playlists")
@construtor_playlist = ConstrutorPlaylist.new(@banco_dados)
end
def teardown
@construtor_playlist.close
end
def test_playlist_vazia
assert_empty(@construtor_playlist.playlist)
end
end
RSpec: Framework Orientado ao Comportamento
RSpec oferece uma abordagem diferente com foco no comportamento esperado do código. Vamos ver um exemplo prático criando um sistema de pontuação de tênis:
require_relative "pontuador_tenis"
RSpec.describe PontuadorTenis do
describe "pontuação básica" do
let(:pontuador) { PontuadorTenis.new }
it "começa com pontuação 0-0" do
expect(pontuador.pontuacao).to eq("0-0")
end
it "faz a pontuação 15-0 se o servidor ganha um ponto" do
pontuador.dar_ponto_para(:servidor)
expect(pontuador.pontuacao).to eq("15-0")
end
it "levanta erro se não conhece o jogador" do
expect { pontuador.dar_ponto_para(:arbitro) }.to raise_error(RuntimeError)
end
end
end
Implementação da Classe PontuadorTenis
class PontuadorTenis
JOGADORES = %i[servidor receptor]
def initialize
@pontuacao = {servidor: 0, receptor: 0}
end
def pontuacao
"#{@pontuacao[:servidor] * 15}-#{@pontuacao[:receptor] * 15}"
end
def dar_ponto_para(jogador)
raise "Jogador desconhecido #{jogador}" unless JOGADORES.include?(jogador)
@pontuacao[jogador] += 1
end
end
Mock Objects: Simulando Dependências
Mock objects são objetos que simulam a API de objetos existentes no sistema. Eles são especialmente úteis para testar comportamento sem depender de recursos externos caros ou frágeis.
Mocks em Minitest
def setup
@banco_dados = Minitest::Mock.new
@banco_dados.expect(:conectar, true)
@banco_dados.expect(:desconectar, false)
@construtor_playlist = ConstrutorPlaylist.new(@banco_dados)
end
def teardown
@banco_dados.desconectar
@banco_dados.verify # Verifica se todas as expectativas foram atendidas
end
Mocks em RSpec
# Criando um double simples
objeto = double(custo: "barato", nome: "banana")
# Verificando se métodos foram chamados
expect(objeto).to receive(:custo).and_return("barato")
objeto.custo
# Stubbing métodos em objetos existentes
kermit = Muppet.new
allow(kermit).to receive(:saudacao).and_return("Oi pessoal")
Matchers do RSpec: Expressividade Natural
RSpec oferece uma sintaxe rica de matchers que torna os testes mais legíveis e próximos da linguagem natural:
# Matchers básicos de lógica
expect(valor).to be_truthy
expect(valor).to be_an_instance_of(Produto)
expect(valor.preco).to be > 10
# Matchers para dados estruturados
expect(array).to contain_exactly(:a, :b, :c)
expect(hash).to include(chave: valor)
expect(string).to start_with("abc")
# Matchers dinâmicos
expect(livro).to be_uma_brochura # Procura por livro.brochura?
expect(livro).to have_autor("Dave") # Procura por livro.has_autor?("Dave")
Organizando e Executando Testes
Uma boa organização de testes é fundamental para projetos de grande escala. Aqui estão algumas práticas recomendadas:
Estrutura de Diretórios
projeto/
lib/
numero_romano.rb
pontuador_tenis.rb
test/
test_numero_romano.rb
test_pontuador_tenis.rb
spec/
numero_romano_spec.rb
pontuador_tenis_spec.rb
Executando Testes
# Executando testes minitest
ruby -I lib test/test_numero_romano.rb
# Executando testes específicos
ruby test/test_numero_romano.rb -n test_conversoes_simples
# Executando testes RSpec
rspec spec/numero_romano_spec.rb
Minitest vs RSpec: Quando Usar Cada Um
Use Minitest quando:
- Preferir simplicidade e compatibilidade com outros frameworks de teste
- Quiser usar apenas a biblioteca padrão do Ruby
- Estiver trabalhando em projetos menores ou com equipes iniciantes
Use RSpec quando:
- Quiser uma sintaxe mais expressiva e próxima da linguagem natural
- Precisar de recursos avançados de mocking e stubbing
- Estiver focado em desenvolvimento orientado ao comportamento (BDD)
Conclusão
Testes automatizados são uma ferramenta essencial para qualquer desenvolvedor Ruby sério. Tanto minitest quanto RSpec oferecem excelentes capacidades, cada um com suas próprias vantagens. Minitest oferece simplicidade e está incluído na biblioteca padrão, enquanto RSpec fornece expressividade e recursos avançados.
O mais importante é começar a escrever testes, independentemente do framework escolhido. À medida que você se familiariza com os testes, descobrirá que eles não apenas melhoram a qualidade do seu código, mas também tornam o desenvolvimento mais rápido e confiável a longo prazo.
Lembre-se: testes não são apenas sobre encontrar bugs - eles são sobre escrever código melhor, mais limpo e mais maintível. Eles são um investimento no futuro do seu projeto.
Nenhum comentário:
Postar um comentário