Texto de: Geraldo Daros
Introdução
Os testes unitários desempenham um papel crucial na garantia da qualidade e confiabilidade do software. Eles permitem que nós desenvolvedores verifiquemos a precisão e integridade de nossos códigos de maneira sistemática e eficiente. Neste artigo, exploraremos como realizar testes unitários em Python de maneira simples e rápida, proporcionando uma base sólida para melhorar a confiabilidade e robustez de seus projetos.
O que são testes unitários?
Testes unitários são uma prática fundamental para garantir a qualidade e confiabilidade do seu código. Através da criação de testes automatizados para cada unidade de código (como funções, classes ou módulos), você pode verificar se elas estão funcionando como esperado e identificar bugs com antecedência.
Finalidade dos testes unitários
Os testes unitários são fundamentais para isolar segmentos específicos de código a fim de avaliá-los precisamente. Em sua essência, esse processo envolve a execução de uma sequência de operações com a expectativa de um resultado específico, seguido pela verificação da correspondência desse resultado com o esperado. Existem muitas maneiras de determinar a aprovação ou falha de um teste, das quais exploraremos as mais comuns.
Um dos principais objetivos dos testes unitários é a prevenção de bugs. Por exemplo, quando uma nova funcionalidade é introduzida, é essencial executar todos os testes da aplicação. Se um teste falhar devido a essa nova funcionalidade, isso indica uma possível fonte de erro. Nesse contexto, é crucial corrigir quaisquer problemas antes de prosseguir com o lançamento da nova funcionalidade.
Benefícios dos testes unitários em Python:
- Detecção precoce de erros: com os testes é possível encontrar bugs antes que afetem os usuários, evitando maiores problemas e economizando tempo e recursos.
- Código mais robusto: testes garantem que o código funcione em diferentes cenários, aumentando a confiabilidade e resiliência da sua aplicação.
- Maior confiança no código: a testagem de código permite que os desenvolvedores façam alterações com mais segurança, sem medo de quebrar funcionalidades existentes.
- Facilidade de refatoração: com testes em vigor, você pode refatorar o código com mais segurança, garantindo que as funcionalidades não sejam afetadas.
Introdução ao unittest:
O unittest é um framework de testes unitários nativo do Python, o que o torna uma opção popular e fácil de usar. Ele fornece uma estrutura para escrever e executar testes, além de oferecer diversos recursos para verificar o comportamento do seu código.
A estrutura de testes unitários unittest foi originalmente inspirada no JUnit e compartilha semelhanças com os principais frameworks de testes unitários em outras linguagens. Ele facilita a automação de testes, o compartilhamento de configuração de código, a organização de testes em coleções e a geração independente de relatórios de teste.
Criando estrutura para usar unittest:
Para começar a usar o unittest
, você precisa criar um novo projeto Python e instalar a biblioteca unittest
. Iremos usar uma estrutura simples para esse projeto que será configurada a partir desses passos:
- Crie um novo diretório para o seu projeto.
- Crie um arquivo Python (
calculadora.py
) com o código que você deseja testar. - Crie um arquivo Python (
test_calculadora.py
) para os seus testes unitários (por convenção usamostest_
seguido do nome do arquivo que está testando, mas funcionará com outro nome também).
Prática:
Coloque o seguinte código no arquivo calculadora.py
:
def soma(a, b):
return a + b
def subtrair(a, b):
return a - b
No nosso arquivo calculadora.py
temos duas funções, uma de somar e outra de subtrair, temos 2 fluxos que precisamos verificar se está funcionando corretamente. O fluxo de somar 2 valores e o fluxo de subtrair dois valores.
Um caso de teste é criado por subclasse da classe unittest.TestCase
. Dentro do unittest, o nome do método que faz um teste deve começar com test já que essa nomenclatura informa ao executor de teste quais métodos representam testes.
Com isso, podemos preencher o nosso arquivo test_calculadora.py
:
import unittest
import calculadora
class TestCalculadora(unittest.TestCase):
def test_soma_dois_numeros_positivos(self):
resultado = calculadora.soma(2, 3)
self.assertEqual(resultado, 5)
def test_soma_dois_numeros_negativos(self):
resultado = calculadora.soma(-2, -3)
self.assertEqual(resultado, -5)
def test_subtrai_dois_numeros_positivos(self):
resultado = calculadora.subtrai(2, 3)
self.assertEqual(resultado, -1)
def test_subtrai_dois_numeros_negativos(self):
resultado = calculadora.subtrai(-2, -3)
self.assertEqual(resultado, 1)
if __name__ == '__main__':
unittest.main()
Explicando o código:
- Neste código definimos uma classe que herda de
unittest.TestCase
isso significa queTestCalculadora
é uma classe de teste e herda métodos e funcionalidades fornecidos pelounittest.TestCase
. - Com a herança de
unittest.TestCase
, a classeTestCalculadora
tem acesso aos métodos e funcionalidades fornecidos porunittest.TestCase
. - O
self
é usado dentro dos métodos de teste para acessar os métodos e atributos da instância da classe de teste atual. - Salvamos o retorno das funções e depois comparamos com o resultado esperado através do
assertEqual()
. - Cada método de teste tem a responsabilidade de testar um cenário específico e está nomeado de forma clara e explícita para representar esse cenário.
- A última parte do código (
if __name__ == '__main__':
) verifica se o script está sendo executado como o programa principal e, se for o caso, executa todos os testes na classeTestCalculadora
utilizandounittest.main()
. Isso inicia a execução dos testes e fornece os resultados no console.
Executando os testes:
Para executar os testes, execute o comando comum para executar um arquivo python: python nome_arquivo_teste.py
no terminal. Nesse exemplo, ficaria python test_calculadora.py
. Se os testes forem bem-sucedidos, você verá a seguinte mensagem:
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
Cenário com bug
Até o momento todos os nossos testes passaram, o código está funcionando perfeitamente! Suponhamos, agora, que desenvolveremos uma nova função para a calculadora, a função de divisão, e para economizar tempo e esforço eu simplesmente copiei outra função que estava criada e nosso arquivo calculadora.py
ficou assim:
def soma(a, b):
return a + b
def subtrai(a, b):
return a - b
def divide(a, b):
return a - b
Copiei, colei e mudei o nome, porém, esqueci de mudar a operação que ele faz, ele continua fazendo subtração, e na hora do teste isso ficará claro.
Vamos agora adicionar mais dois testes referentes ao método divide na nossa classe de testes:
def test_divide_dois_numeros_positivos(self):
resultado = calculadora.divide(10, 2)
self.assertEqual(resultado, 5)
def test_divide_dois_numeros_negativos(self):
resultado = calculadora.divide(-5, -2)
self.assertEqual(resultado, 2.5)
Esse código gerará erro ao rodar, porque o resultado não será o esperado, justamente porque a função divide() não tem o comportamento esperado. Quando o erro acontecer, também será mostrado no console um log de erro porque algum teste não passou.
Log de erro
O log de erro mostrará o porquê do teste não ter passado e informações para que você possa identificar em qual arquivo e qual linha está o problema, e também o resultado esperado e o resultado obtido.
Log obtido do bug anterior:
FF....
======================================================================
FAIL: test_divide_dois_numeros_negativos (__main__.TestCalculadora.test_divide_dois_numeros_negativos)
----------------------------------------------------------------------
Traceback (most recent call last):
File "...\test_calculadora.py", line 30, in test_divide_dois_numeros_negativos
self.assertEqual(resultado, 2.5)
AssertionError: -3 != 2.5
======================================================================
FAIL: test_divide_dois_numeros_positivos (__main__.TestCalculadora.test_divide_dois_numeros_positivos)
----------------------------------------------------------------------
Traceback (most recent call last):
File "...\test_calculadora.py", line 26, in test_divide_dois_numeros_positivos
self.assertEqual(resultado, 5)
AssertionError: 8 != 5
----------------------------------------------------------------------
Ran 6 tests in 0.001s
FAILED (failures=2)
Ele mostra o arquivo que teve testes que não passaram: File "...\\test_calculadora.py", line 30, in test_divide_dois_numeros_negativos
.
Ele mostra qual expressão não passou: self.assertEqual(resultado, 5)
. E também mostra e resultado obtido, comparação e esperado, respectivamente: AssertionError: 8 != 5
nesse caso o valor obtido é 8, a comparação é ≠ e o esperando é 5 e no final ele me dá uma estatística de quantos testes falharam: FAILED (failures=2)
.
Com todas essas informações, fica fácil encontrar onde o problema está sendo originado e começar a trabalhar em uma correção para ele e com os diferentes cenários de teste podemos também avaliar se esse problema acontece apenas em um caso específico, ou em vários casos de teste.
Métodos para comparação
Vimos anteriormente que o testes são feitos por meio de métodos de comparação. Não mostrarei todos os métodos de comparações possíveis, mas aqui segue os métodos de comparação mais utilizados:
Método | Checagem |
---|---|
assertEqual(a, b) | a == b |
assertNotEqual(a, b) | a != b |
assertTrue(x) | bool(x) is True |
assertFalse(x) | bool(x) is False |
assertIs(a, b) | a is b |
assertIsNot(a, b) | a is not b |
assertIsNone(x) | x is None |
assertIsNotNone(x) | x is not None |
assertIn(a, b) | a in b |
assertNotIn(a, b) | a not in b |
assertIsInstance(a, b) | isinstance(a, b) |
assertNotIsInstance(a, b) | not isinstance(a, b) |
Essa tabela está na documentação do unittest.
Ela demonstra os métodos mais usados para fazer teste usando o unittest, e você deve escolher o método que mais se encaixa na sua necessidade, não existe o melhor ou pior, e sim o ideal para o seu contexto e seu teste.
Conclusão
Testes unitários são uma ferramenta essencial para garantir a qualidade do seu código Python. O framework unittest oferece uma maneira fácil de escrever e executar testes, ajudando você a identificar bugs e melhorar a confiabilidade do seu software. Nesse artigo você pode ver na prática como começar a desenvolver seus próprios testes e melhor a confiança do seu software.