Introdução a testes
Nesta aula, você aprenderá a importância de testar código em Python, as diferenças entre unittest e pytest, como escrever seu primeiro teste e a estrutura recomendada para organizar testes. O objetivo é capacitar você a criar testes simples e eficazes para seus projetos.
Testar código é uma prática essencial no desenvolvimento de software, pois garante que o comportamento esperado seja mantido ao longo do tempo e que mudanças no código não introduzam erros inesperados. Nesta aula, vamos explorar os fundamentos de testes em Python, começando pela motivação, passando pelas principais ferramentas (unittest e pytest), e culminando com a criação do seu primeiro teste e a estrutura adequada para organizá-los.
Você aprenderá não apenas a escrever testes, mas também a pensar criticamente sobre o que testar e como estruturar seus projetos para facilitar a manutenção e a expansão dos testes. Ao final, você terá uma base sólida para incorporar testes no seu fluxo de desenvolvimento.
Por que testar
Testar é uma forma de verificar se o código faz o que deveria fazer. Sem testes, você depende de testes manuais ou da confiança de que as mudanças não quebram nada, o que é arriscado em projetos de qualquer tamanho. Testes automatizados permitem que você valide rapidamente o comportamento do código após cada alteração, aumentando a confiança e reduzindo a ocorrência de bugs.
Além disso, testes bem escritos funcionam como documentação executável: eles mostram como o código deve ser usado e quais resultados são esperados. Isso é valioso para novos desenvolvedores que entram no projeto e para você mesmo, meses depois, quando precisar lembrar o que determinada função faz.
Benefícios adicionais incluem:
- Detecção precoce de erros: testes rodam automaticamente e identificam problemas antes que cheguem ao usuário final.
- Refatoração segura: você pode modificar o código com confiança, sabendo que os testes vão pegar qualquer regressão.
- Melhor design: escrever testes força você a pensar em interfaces claras e responsabilidades bem definidas.
- Integração contínua: testes são a base para pipelines de CI/CD, que automatizam a validação do código.
unittest vs pytest
Python vem com um framework de testes embutido chamado unittest, que é baseado no padrão xUnit. Ele oferece classes de teste, métodos de asserção e um runner de testes. Por outro lado, pytest é uma biblioteca de terceiros que se tornou muito popular por sua simplicidade e recursos avançados, como fixtures, parametrização e plugins.
unittest tem a vantagem de não exigir instalação adicional e ser suficiente para projetos simples. No entanto, sua sintaxe é mais verbosa, exigindo que você crie classes que herdam de unittest.TestCase e use métodos de asserção específicos. Exemplo:
import unittest
def soma(a, b):
return a + b
class TestSoma(unittest.TestCase):
def test_soma_positivos(self):
self.assertEqual(soma(2, 3), 5)
if __name__ == '__main__':
unittest.main()pytest, por sua vez, permite escrever testes como funções simples, sem a necessidade de classes. Ele usa a instrução assert do Python, o que torna o código mais conciso e legível. Além disso, pytest fornece muitas funcionalidades úteis, como captura de saída, fixtures e relatórios detalhados. Exemplo:
def soma(a, b):
return a + b
def test_soma_positivos():
assert soma(2, 3) == 5Para instalar pytest, use pip install pytest. Em termos de escolha, pytest é frequentemente recomendado para novos projetos devido à sua facilidade de uso e ecossistema rico. No entanto, unittest ainda é uma opção válida, especialmente se você prefere não adicionar dependências externas ou precisa de compatibilidade com ferramentas que dependem dele.
Primeiro teste
Vamos escrever nosso primeiro teste usando pytest, pois é mais direto. Suponha que temos um módulo chamado calculadora.py com uma função soma:
# calculadora.py
def soma(a, b):
return a + bAgora, criamos um arquivo de teste chamado test_calculadora.py (por convenção, os arquivos de teste começam com test_ ou terminam com _test.py):
# test_calculadora.py
from calculadora import soma
def test_soma_positivos():
assert soma(2, 3) == 5
def test_soma_negativos():
assert soma(-1, -1) == -2Para rodar os testes, execute pytest no terminal a partir do diretório onde estão os arquivos. O pytest irá descobrir automaticamente os arquivos de teste e executar as funções que começam com test_. A saída mostrará se os testes passaram ou falharam.
Você também pode rodar com mais detalhes usando pytest -v para ver cada teste individualmente. Em caso de falha, o pytest exibirá o valor dos asserts, facilitando a depuração.
Estrutura
Uma estrutura de testes bem organizada é crucial para a manutenibilidade. A convenção comum em projetos Python é colocar os testes em um diretório separado, geralmente chamado tests. Dentro dele, você pode espelhar a estrutura do pacote principal, facilitando a localização dos testes correspondentes a cada módulo.
Exemplo de estrutura de projeto:
meu_projeto/
├── meu_pacote/
│ ├── __init__.py
│ ├── calculadora.py
│ └── utils.py
└── tests/
├── __init__.py
├── test_calculadora.py
└── test_utils.pyAlém disso, é importante usar nomes de testes descritivos, como test_soma_retorna_correto_para_positivos, para que falhas sejam facilmente identificáveis. No pytest, você também pode usar fixtures para configurar pré-condições e recursos compartilhados, e parametrização para testar múltiplos casos com um único teste.
Boas práticas adicionais incluem:
- Isolamento: cada teste deve ser independente e não depender de outros testes.
- Testes pequenos: foque em testar uma única funcionalidade por teste.
- Use mocks quando necessário: para isolar testes de dependências externas, como bancos de dados ou APIs.
- Rode os testes frequentemente: integre-os ao seu fluxo de trabalho, usando ferramentas como pre-commit ou CI.
Referências
- Documentação oficial do unittest
- Documentação oficial do pytest
- Como usar assert no pytest
- Fixtures no pytest
- Controle de qualidade na biblioteca padrão
- Tutorial de pytest no Real Python
Exercícios
- Escreva um teste para uma função
subtrai(a, b)que retorna a subtração de dois números, usando pytest. Crie o arquivo de teste e execute-o. - Converta o teste do exercício 1 para usar
unittestem vez de pytest. Escreva a classe de teste correspondente. - Crie uma estrutura de diretórios para um projeto chamado
meu_projetocom um pacotemeu_pacotecontendo um módulocalculadora.pye um diretóriotestscom um arquivo de teste para a funçãosoma. Escreva os arquivos e execute os testes. - Escreva um teste parametrizado usando pytest para verificar a função
multiplica(a, b)com vários pares de números, incluindo zero e negativos. - Explique a diferença entre unittest e pytest em termos de sintaxe e flexibilidade, e dê um exemplo de uma situação em que você preferiria usar um em vez do outro.
test_calculadora.py com o seguinte conteúdo:from calculadora import subtrai
def test_subtrai_positivos():
assert subtrai(5, 3) == 2
def test_subtrai_negativos():
assert subtrai(-1, -2) == 1Execute pytest no terminal.import unittest
from calculadora import subtrai
class TestSubtrai(unittest.TestCase):
def test_subtrai_positivos(self):
self.assertEqual(subtrai(5, 3), 2)
def test_subtrai_negativos(self):
self.assertEqual(subtrai(-1, -2), 1)
if __name__ == '__main__':
unittest.main()meu_projeto/
├── meu_pacote/
│ ├── __init__.py
│ └── calculadora.py
└── tests/
├── __init__.py
└── test_calculadora.pyConteúdo de calculadora.py:def soma(a, b):
return a + bConteúdo de test_calculadora.py:from meu_pacote.calculadora import soma
def test_soma():
assert soma(2, 3) == 5Execute pytest a partir do diretório meu_projeto.@pytest.mark.parametrize:import pytest
from calculadora import multiplica
@pytest.mark.parametrize("a,b,esperado", [
(2, 3, 6),
(0, 5, 0),
(-2, 3, -6),
(2, -3, -6),
])
def test_multiplica(a, b, esperado):
assert multiplica(a, b) == esperadounittest usa classes e métodos de asserção específicos (ex.: assertEqual), enquanto pytest usa funções simples e a instrução assert nativa. pytest é mais conciso e oferece recursos como fixtures, parametrização e plugins, enquanto unittest é embutido e não requer dependências externas. Prefira unittest em projetos que precisam de compatibilidade máxima sem instalações adicionais, ou quando já está integrado com outras ferramentas. Prefira pytest para novos projetos, especialmente se você valoriza produtividade e recursos avançados.
Boas práticas
Além do que já discutimos, lembre-se de manter seus testes rápidos e confiáveis. Evite dependências de rede ou de tempo real nos testes unitários, utilizando mocks ou stubs quando necessário. Também é útil rodar os testes com frequência, por exemplo, após cada alteração no código, para detectar problemas cedo. Considere adotar uma ferramenta de cobertura de código, como pytest-cov, para identificar áreas não testadas e melhorar a qualidade geral.