Testes Unitários em Python: Guia Completo com `unittest`

Todo desenvolvedor de qualidade sabe que não se deve implementar código sem testes abrangentes. O teste unitário é o processo de avaliação dos componentes individuais que constituem um programa maior.

Este artigo explicará como você pode realizar testes unitários em seu código utilizando o módulo `unittest` do Python. Primeiramente, vamos entender os diversos tipos de testes existentes.

No contexto de testes, existem testes manuais e testes automatizados. Testes manuais envolvem a execução de testes por pessoas após o desenvolvimento ser concluído. Já os testes automatizados são conduzidos por programas que executam os testes automaticamente e fornecem os resultados.

É fácil perceber que os testes manuais demandam tempo e esforço. Por essa razão, os desenvolvedores criam códigos para realizar testes (testes automatizados). Dentro dos testes automatizados, há diferentes categorias, como testes unitários, testes de integração, testes de ponta a ponta e testes de estresse, entre outros.

Vamos analisar o fluxo padrão de testes:

  • Escreva ou atualize o código.
  • Escreva ou modifique testes para as diversas situações possíveis do seu código.
  • Execute os testes (manualmente ou através de um executor de testes).
  • Examine os resultados dos testes. Se forem identificados erros, corrija-os e repita o processo.

Neste artigo, vamos nos aprofundar no tipo de teste mais fundamental, conhecido como teste unitário. Sem mais delongas, vamos começar o tutorial.

O que é Teste Unitário?

O teste unitário é uma técnica que avalia um pequeno bloco de código isoladamente. Geralmente, esse bloco de código é uma função. O termo “isolado” significa que ele não depende de outras partes do código dentro do projeto.

Suponha que precisamos verificar se uma string é igual a “etechpt.com”. Para isso, criamos uma função que recebe uma string como argumento e retorna verdadeiro caso seja igual a “etechpt.com” e falso caso contrário.

def is_equal_to_geekflare(string):
	return string == "etechpt.com"

A função apresentada não depende de nenhum outro código. Sendo assim, podemos testá-la de forma independente, fornecendo diferentes entradas. Esse tipo de código pode ser reutilizado em todo o projeto.

A importância do teste unitário

Em geral, os blocos de código independentes podem ser utilizados em diversas partes do projeto. Portanto, é fundamental que sejam bem escritos e testados. Os testes unitários são as ferramentas adequadas para avaliar esses blocos de código isolados. O que aconteceria se não utilizássemos testes unitários em nosso projeto?

Imagine que não testamos os pequenos blocos de código que são amplamente utilizados no projeto. Outros testes, como testes de integração, testes de ponta a ponta, etc., que utilizam esses blocos de código podem apresentar falhas. Isso comprometeria a integridade do aplicativo. Por isso, é crucial que os blocos de construção básicos do código sejam testados adequadamente.

Agora que entendemos a importância do teste unitário, sabemos que eles devem ser criados para todos os blocos de código independentes. Dessa forma, outros testes, como os de integração e ponta a ponta, não serão afetados por falhas nos blocos de código independentes.

Nas próximas seções, vamos explorar o módulo `unittest` do Python e como ele pode ser utilizado para escrever testes unitários em Python.

Observação: Assumimos que você já tem familiaridade com classes, módulos e outros conceitos do Python. Se você não estiver familiarizado com esses conceitos intermediários do Python, as próximas seções podem ser um pouco desafiadoras.

O que é o `unittest` do Python?

O `unittest` do Python é um framework de testes integrado que permite testar o código Python. Ele inclui um executor de testes, o que facilita a execução dos testes. Com isso, podemos usar o módulo `unittest` sem a necessidade de módulos de terceiros, embora essa decisão dependa das necessidades do projeto. O módulo `unittest` integrado é uma excelente opção para começar a testar em Python.

Para testar o código Python usando o módulo `unittest`, devemos seguir os passos a seguir:

#1. Escreva o código.

#2. Importe o módulo `unittest`.

#3. Crie um arquivo de teste cujo nome comece com a palavra “test”, por exemplo, `test_prime.py`. A palavra “test” é usada para identificar os arquivos de teste.

#4. Crie uma classe que herde da classe `unittest.TestCase`.

#5. Escreva métodos (testes) dentro da classe, sendo que cada método deve conter diferentes casos de teste, de acordo com as suas necessidades. O nome desses métodos deve começar com a palavra “test”.

#6. Execute os testes. Eles podem ser executados de diferentes formas:

  • Execute o comando `python -m unittest test_filename.py`.
  • Execute os arquivos de teste como arquivos Python normais através do comando `python test_filename.py`. Para que este método funcione, é necessário invocar o método `main` do `unittest` no arquivo de teste.
  • Utilizando o método “Discover”. Este método permite executar os testes automaticamente com o comando `python -m unittest discover`, sem a necessidade de especificar o nome do arquivo de teste. Ele encontrará os testes com base na convenção de nomenclatura, o que significa que os nomes dos arquivos de teste devem começar com a palavra “test”.

Geralmente, em testes, comparamos a saída do código com a saída esperada. Para realizar essas comparações, o `unittest` oferece diversos métodos. Você pode encontrar uma lista completa de funções de comparação aqui.

Esses métodos são fáceis de entender e usar.

Essa é uma boa quantidade de teoria. Agora, vamos para a parte prática.

Observação: Se você tiver dúvidas sobre o módulo `unittest`, consulte a documentação oficial. Sem mais delongas, vamos começar a usar o módulo `unittest`.

Testes unitários em Python usando o `unittest`

Primeiramente, vamos escrever algumas funções. Depois, nos concentraremos em escrever os testes. Abra uma pasta no seu editor de código e crie um arquivo chamado `utils.py`. Insira o seguinte código no arquivo:

import math

def is_prime(n):
    if n < 0:
        return 'Números negativos não são permitidos'

    if n <= 1:
        return False

    if n == 2:
        return True

    if n % 2 == 0:
        return False

    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True


def cubic(a):
    return a * a * a

def say_hello(name):
    return "Olá, " + name

O arquivo `utils.py` contém três funções distintas. Agora, precisamos testar cada função usando diferentes casos de teste. Começaremos escrevendo os testes para a primeira função, `is_prime`.

#1. Crie um arquivo chamado `test_utils.py` na mesma pasta que o arquivo `utils.py`.

#2. Importe os módulos `utils` e `unittest`.

#3. Crie uma classe chamada `TestUtils` que herde da classe `unittest.TestCase`. O nome da classe pode ser qualquer um, mas é recomendável escolher um nome que seja significativo.

#4. Dentro da classe, crie um método chamado `test_is_prime` que aceite `self` como argumento.

#5. Escreva diferentes casos de teste, fornecendo diferentes argumentos para a função `is_prime` e comparando a saída com a saída esperada.

#6. Exemplo de caso de teste: `self.assertFalse(utils.is_prime(1))`.

#7. No caso acima, esperamos que a saída de `is_prime(1)` seja `False`.

#8. De maneira semelhante, criaremos diferentes casos de teste, de acordo com a função que estiver sendo testada.

A seguir, apresentamos os testes:

import unittest
import utils

class TestUtils(unittest.TestCase):
    def test_is_prime(self):
        self.assertFalse(utils.is_prime(4))
        self.assertTrue(utils.is_prime(2))
        self.assertTrue(utils.is_prime(3))
        self.assertFalse(utils.is_prime(8))
        self.assertFalse(utils.is_prime(10))
        self.assertTrue(utils.is_prime(7))
        self.assertEqual(utils.is_prime(-3),
                         "Números negativos não são permitidos")

if __name__ == '__main__':
    unittest.main()

Estamos invocando o método `main` do módulo `unittest` para executar os testes através do comando `python filename.py`. Agora, execute os testes.

Você deverá ver uma saída similar a esta:

$ python test_utils.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Agora, tente escrever os casos de teste para as outras funções também. Pense em diferentes situações para cada função e escreva testes para elas. Veja os testes adicionados à classe abaixo:

...

class TestUtils(unittest.TestCase):
    def test_is_prime(self):
        ...

    def test_cubic(self):
        self.assertEqual(utils.cubic(2), 8)
        self.assertEqual(utils.cubic(-2), -8)
        self.assertNotEqual(utils.cubic(2), 4)
        self.assertNotEqual(utils.cubic(-3), 27)

    def test_say_hello(self):
        self.assertEqual(utils.say_hello("etechpt.com"), "Olá, etechpt.com")
        self.assertEqual(utils.say_hello("Chandan"), "Olá, Chandan")
        self.assertNotEqual(utils.say_hello("Chandan"), "Oi, Chandan")
        self.assertNotEqual(utils.say_hello("Hafeez"), "Oi, Hafeez")

...

Utilizamos apenas algumas das funções de comparação do módulo `unittest`. Você pode encontrar a lista completa aqui.

Agora, aprendemos como escrever testes unitários usando o módulo `unittest`. É hora de analisar as diferentes formas de executar os testes.

Como executar testes utilizando o `unittest`

Já vimos uma maneira de executar os casos de teste na seção anterior. Agora, vamos examinar outras duas formas de executar os testes usando o módulo `unittest`.

#1. Utilizando o nome do arquivo e o módulo `unittest`.

Nesse método, usaremos o módulo `unittest` e o nome do arquivo para executar os testes. O comando para executar os testes é `python -m unittest filename.py`. No nosso caso, o comando para executar os testes é `python -m unittest test_utils.py`.

#2. Utilizando o método de descoberta

Nesse método, utilizaremos a funcionalidade de descoberta do módulo `unittest` para identificar e executar automaticamente todos os arquivos de teste. Para que os arquivos de teste sejam identificados automaticamente, seus nomes devem começar com a palavra “test”.

O comando para executar os testes utilizando o método Discover é `python -m unittest discover`. Este comando detectará e executará todos os arquivos cujos nomes começam com a palavra “test”.

Conclusão 👩‍💻

Os testes unitários são testes essenciais no universo da programação. Existem diversos outros tipos de testes no mundo real. É recomendado que você os aprenda gradualmente. Espero que este tutorial tenha lhe ajudado a escrever testes básicos em Python usando o módulo `unittest`. Existem bibliotecas de terceiros, como `pytest`, `Robot Framework`, `nose`, `nose2`, `slash`, etc. Explore-as de acordo com as necessidades de seus projetos.

Bons testes 😎

Você também pode se interessar por Perguntas e respostas de entrevista em Python.