Fundamentos de Testes Automatizados
Os testes automatizados são a espinha dorsal do desenvolvimento moderno. Enquanto testes manuais consomem tempo e são propensos a erros humanos, os testes automatizados executam verificações repetitivas com precisão, permitindo que você implante código com confiança. A pirâmide de testes apresenta três níveis: testes unitários (base), testes de integração (meio) e testes end-to-end (topo). Comece pelos unitários — são rápidos, isolados e testam uma única unidade de código.
Vamos implementar um teste unitário básico em Python usando pytest. Considere uma função que calcula o preço final com desconto:
def aplicar_desconto(preco, percentual_desconto):
if percentual_desconto < 0 or percentual_desconto > 100:
raise ValueError("Desconto deve estar entre 0 e 100")
return preco * (1 - percentual_desconto / 100)
# Teste unitário
def test_aplicar_desconto_valido():
assert aplicar_desconto(100, 10) == 90.0
assert aplicar_desconto(50, 50) == 25.0
def test_aplicar_desconto_invalido():
with pytest.raises(ValueError):
aplicar_desconto(100, 150)
Execute com pytest nome_arquivo.py. Cada teste é isolado, testando um comportamento específico. Isso garante que refatorações futuras não quebrem funcionalidades esperadas.
Testes de Integração e Mocks
Testes de integração verificam como múltiplos componentes trabalham juntos. Aqui entra em jogo o uso de mocks — objetos simulados que substituem dependências externas como APIs, bancos de dados ou serviços. Isso permite testar lógica sem depender de sistemas externos.
Considere uma aplicação que busca dados de um usuário em uma API:
from unittest.mock import patch, MagicMock
import requests
def buscar_usuario(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
if response.status_code == 200:
return response.json()
raise Exception("Usuário não encontrado")
@patch('requests.get')
def test_buscar_usuario_sucesso(mock_get):
mock_get.return_value = MagicMock(status_code=200)
mock_get.return_value.json.return_value = {"id": 1, "nome": "João"}
resultado = buscar_usuario(1)
assert resultado["nome"] == "João"
mock_get.assert_called_once_with("https://api.example.com/users/1")
@patch('requests.get')
def test_buscar_usuario_erro(mock_get):
mock_get.return_value = MagicMock(status_code=404)
with pytest.raises(Exception):
buscar_usuario(1)
O decorator @patch substitui a função real por um mock. Você controla o comportamento e verifica se as chamadas ocorreram corretamente. Isso torna testes rápidos e confiáveis, sem dependências externas.
Testes End-to-End e Automação de Interface
Testes end-to-end (E2E) simulam a jornada real do usuário, testando a aplicação completa do navegador até o banco de dados. Selenium e Playwright são frameworks populares para essa automação. Eles controlam navegadores reais, clicam em elementos e verificam resultados.
Aqui está um exemplo com Playwright em Python:
from playwright.sync_api import sync_playwright
def test_login_usuario():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# Navegar até a aplicação
page.goto("https://exemplo.com/login")
# Preencher formulário
page.fill("input[name='email']", "usuario@example.com")
page.fill("input[name='senha']", "senha123")
page.click("button:has-text('Entrar')")
# Aguardar redirecionamento e verificar
page.wait_for_url("https://exemplo.com/dashboard")
assert page.is_visible("h1:has-text('Dashboard')")
browser.close()
Testes E2E são lentos e frágeis se não bem estruturados — use-os para fluxos críticos. Dica profissional: mantenha seletores CSS descritivos e evite esperas fixas (use wait_for_* ao invés de time.sleep()).
Boas Práticas e Estratégias Avançadas
Cobertura de Testes e Métricas
A cobertura de testes indica qual percentual do código é executado durante testes. Use coverage em Python para medir:
pip install coverage
coverage run -m pytest
coverage report
coverage html # Gera relatório HTML detalhado
Busque 80-90% de cobertura em código crítico, não 100% — é overkill e caro. Foque em lógica de negócio complexa.
Estrutura AAA e Nomenclatura Clara
Organize seus testes com a estrutura Arrange-Act-Assert:
def test_carrinho_adiciona_produto():
# Arrange: preparar dados
carrinho = Carrinho()
produto = Produto("Notebook", 3000)
# Act: executar ação
carrinho.adicionar(produto)
# Assert: verificar resultado
assert len(carrinho.itens) == 1
assert carrinho.total == 3000
Nomeie testes descritivamente: test_[o_que_esta_sendo_testado]_[condicao]_[resultado_esperado].
Testes Parametrizados
Evite repetição usando parametrização:
import pytest
@pytest.mark.parametrize("entrada,esperado", [
(2, 4),
(3, 9),
(-1, 1),
])
def test_quadrado(entrada, esperado):
assert entrada ** 2 == esperado
Um teste, múltiplos cenários. Isso torna suites de testes mais concisas e eficientes.
Conclusão
Testes automatizados são investimento essencial em qualidade e velocidade. Comece com testes unitários para lógica isolada, use mocks para integração sem dependências externas, e implemente E2E para fluxos críticos. Mantenha testes simples, legíveis e rápidos — uma suite lenta é um bloqueio ao desenvolvimento. Com disciplina, seus testes se tornarão documentação viva do comportamento esperado do sistema, permitindo refatorar com segurança e entregar valor continuamente.
Referências
- Pytest Documentation — Documentação oficial do framework pytest
- Unittest Mock — Python Docs — Guia completo de mocks em Python
- Playwright Documentation — Framework para automação E2E
- Testing Library Best Practices — Princípios modernos de teste
- Growing Object-Oriented Software, Guided by Tests — Livro essencial sobre TDD