Blog Formação DEV

Testes unitários em Python usando unittest

Veja como realizar testes em Python de maneira eficiente e direta, aprimorando a confiabilidade e robustez de seus projetos.
Testes unitários em Python usando unittest
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:

  1. Crie um novo diretório para o seu projeto.
  2. Crie um arquivo Python (calculadora.py) com o código que você deseja testar.
  3. Crie um arquivo Python (test_calculadora.py) para os seus testes unitários (por convenção usamos test_ 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 que TestCalculadora é uma classe de teste e herda métodos e funcionalidades fornecidos pelo unittest.TestCase.
  • Com a herança de unittest.TestCase, a classe TestCalculadora tem acesso aos métodos e funcionalidades fornecidos por unittest.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 classe TestCalculadora utilizando unittest.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.

Recursos Adicionais:

Leia também:

Sobre o autor
Cod3r

Cod3r

Com mais de 400 mil alunos, a Cod3r é uma das principais escolas de tecnologia do País. Um de seus produtos mais importantes é a Formação DEV, com objetivo de preparar os profissionais para o mercado.

Ótimo! Inscreveu-se com sucesso.

Bem-vindo de volta! Registou-se com sucesso.

Assinou com sucesso o Blog Formação DEV .

Sucesso! Verifique o seu e-mail para obter o link mágico para se inscrever.

As suas informações de pagamento foram atualizadas.

Seu pagamento não foi atualizado.