Novidades

Testes unitarios Python: guia passo a passo para iniciantes

ResumoTestes unitários Python são práticas essenciais para evitar quebras de sistema após commits. O guia passo a passo para iniciantes ensina a estruturar testes funcionais e diretos, sem complexidade acadêmica, com exemplos de código aplicável. A abordagem prática protege o código contra erros comuns e garante maior confiabilidade no desenvolvimento.

Testes unitários em Python parecem frescura até o dia em que você quebra o sistema com um commit inocente. Neste guia, vou te mostrar como estruturar testes que salvam sua pele, sem frescura acadêmica, com código que funciona.

Igor Bastos
Microservices arquitetura: o que é e como aplicar

Microservices arquitetura: o que é e como aplicar — Foto: Reprodução / Blog Sem Juízo

Eu já quebrei um sistema inteiro com um print() esquecido. Não foi bonito. O deploy foi pro saco, o lead me olhou com aquela cara de 'você testou isso?' e eu, com a maior cara de pau, menti que sim. Desde aquele dia, testes unitários em Python viraram meu mantra de sobrevivência.

Testes unitários em Python são pequenos programas que verificam se cada pedaço do seu código, uma função, um método, uma classe, funciona como esperado. Eles não garantem que seu sistema esteja livre de bugs, mas garantem que você não vai quebrar algo que já funcionava. Neste guia passo a passo, vou te mostrar como estruturar testes unitários em Python de um jeito que até seu eu do futuro vai agradecer.

Passo 1: Escolha sua ferramenta de teste

Antes de escrever um único assert, você precisa decidir qual framework usar. O Python vem com o unittest embutido, que é robusto e não exige instalação. Mas o pytest é mais conciso, com menos boilerplate e mensagens de erro mais legíveis. Para iniciantes, recomendo começar com unittest, ele está sempre lá, não quebra, e você aprende os fundamentos. Depois migra para pytest quando sentir que a verbosidade te incomoda.

Dica: Se for usar pytest, instale com pip install pytest. Se for usar unittest, nada precisa instalar.

Passo 2: Crie a estrutura de pastas

Não jogue tudo na mesma pasta. Uma estrutura padrão ajuda você e qualquer outra pessoa que bater no seu código:

meu_projeto/ ├── src/ │ └── calculadora.py └── tests/ └── test_calculadora.py

Simples, né? O módulo que você vai testar fica em src/ e os testes em tests/. O nome do arquivo de teste começa com test_, isso é convenção tanto do unittest quanto do pytest. Se você fizer isso, o próprio framework descobre os testes automaticamente.

Erro comum: Colocar os testes dentro da mesma pasta do código fonte. Isso polui o diretório e dificulta a execução seletiva.

Passo 3: Escreva seu primeiro teste

Vamos supor que você tem uma função simples em calculadora.py:

def soma(a, b): return a + b

Agora, em test_calculadora.py, escreva:

import unittest from src.calculadora import soma

class TestCalculadora(unittest.TestCase): def test_soma_positivos(self): resultado = soma(2, 3) self.assertEqual(resultado, 5)

Repare no padrão: a classe herda de unittest.TestCase, cada método começa com test_, e dentro dele você chama a função e compara o resultado com assertEqual. Se o resultado for diferente de 5, o teste falha.

Dica: Um teste deve testar UMA coisa só. Não coloque dois assert no mesmo método se eles testam cenários diferentes.

Passo 4: Execute o teste

No terminal, dentro da pasta raiz do projeto:

python -m unittest tests/test_calculadora.py

Se tudo estiver certo, você vê:

.

Ran 1 test in 0.001s

OK

Um ponto significa teste passou. Se quiser mais detalhes, use -v:

python -m unittest -v tests/test_calculadora.py

Erro comum: Executar o comando de dentro da pasta tests/. O Python precisa enxergar o módulo src como parte do caminho de importação. Execute sempre da raiz.

Passo 5: Teste múltiplos cenários

Uma função raramente recebe só números positivos. Você precisa testar:

  • Números negativos
  • Zero
  • Números grandes
  • Tipos inesperados (string, None)

def test_soma_negativos(self): self.assertEqual(soma(-1, -2), -3)

def test_soma_zero(self): self.assertEqual(soma(0, 0), 0)

def test_soma_grandes(self): self.assertEqual(soma(1000000, 2000000), 3000000)

Cada cenário vira um método separado. Se um falhar, você sabe exatamente qual caso deu problema.

Dica: Use nomes descritivos como test_soma_com_numero_negativo em vez de test_soma_2. Seu eu do futuro vai agradecer.

Passo 6: Teste exceções

E se sua função deve lançar um erro quando recebe argumentos inválidos? Teste isso também:

def test_soma_tipo_invalido(self): with self.assertRaises(TypeError): soma("a", 2)

O assertRaises verifica se o código dentro do with realmente lança a exceção esperada. Se não lançar, o teste falha.

Erro comum: Testar exceções sem o with. Escrever self.assertRaises(TypeError, soma, "a", 2) funciona, mas é menos legível. Prefira o with.

Passo 7: Use setUp para preparar o ambiente

Se vários testes precisam do mesmo objeto ou configuração, use setUp:

class TestCalculadora(unittest.TestCase): def setUp(self): self.calc = Calculadora()

def test_soma(self): self.assertEqual(self.calc.soma(2, 3), 5)

O setUp é executado antes de cada teste. Assim você não repete código e mantém os testes independentes.

Dica: Nunca compartilhe estado entre testes com variáveis de classe. Cada teste deve começar do zero.

Passo 8: Organize os testes em suites

Conforme seu projeto cresce, você pode agrupar testes relacionados em suites:

suite = unittest.TestLoader().loadTestsFromTestCase(TestCalculadora) unittest.TextTestRunner().run(suite)

Ou simplesmente execute todos os testes de uma vez:

python -m unittest discover -s tests

O discover encontra automaticamente todos os arquivos que começam com test_ dentro da pasta tests/.

Erro comum: Nomear arquivos de teste sem o prefixo test_. O discover simplesmente ignora.

Checklist do que você aprendeu

  • [ ] Escolheu entre unittest e pytest
  • [ ] Criou estrutura de pastas separada (src/ e tests/)
  • [ ] Escreveu pelo menos um teste com assertEqual
  • [ ] Executou os testes pelo terminal
  • [ ] Testou múltiplos cenários (positivo, negativo, zero, exceção)
  • [ ] Usou setUp para evitar repetição
  • [ ] Executou todos os testes com discover

Agora vai lá e testa seu código. Se quebrar, pelo menos você vai saber exatamente onde.

FAQ

Qual a diferença entre unittest e pytest?

unittest é nativo do Python, mais verboso e com sintaxe mais rígida. pytest é de terceiros, mais conciso, com menos código boilerplate e mensagens de erro mais claras. Para projetos pequenos, unittest já resolve. Para projetos grandes, pytest economiza tempo.

Preciso testar funções privadas (com underscore)?

Em geral, não. Teste a interface pública. Se a função privada é complexa, considere extraí-la para um módulo separado. Testar privadas acopla o teste à implementação e quebra fácil.

Como testar código que faz requisições HTTP?

Use mocks. Com unittest.mock.patch, você substitui a função de requisição por uma simulada que retorna dados controlados. Isso evita chamadas reais e torna o teste rápido e confiável.

Meu teste está lento. O que fazer?

Verifique se você está fazendo operações de I/O (arquivo, rede, banco) dentro do teste. Use mocks para substituir essas operações. Testes unitários devem rodar em milissegundos. Se demorar segundos, algo está errado.

Como nomear os métodos de teste?

Use o padrão test_[nome_da_funcao]_[cenario]. Exemplo: test_soma_com_negativos. Isso torna a falha autoexplicativa. Evite nomes genéricos como test_1 ou test_funcionalidade.

Posso misturar unittest e pytest no mesmo projeto?

Tecnicamente sim, mas não é recomendado. Cada framework tem seu próprio executor e convenções. Escolha um e mantenha consistência. Migrar depois é relativamente fácil, mas manter dois ao mesmo tempo é confuso.

Igor Bastos

Editoria Novidades

Igor Bastos cobre o setor de meios de pagamento e crédito no Blog Sem Juízo. Análises técnicas, sem viés comercial.

Leia também · Novidades