Módulo Unittest do Python

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Neste laboratório, você aprenderá como usar o módulo unittest embutido do Python. Este módulo oferece uma estrutura para organizar e executar testes, o que é uma parte crucial do desenvolvimento de software para garantir que seu código funcione corretamente.

Você também aprenderá a criar e executar casos de teste básicos, e testar erros e exceções esperadas. O módulo unittest simplifica o processo de criação de suítes de teste, execução de testes e verificação de resultados. O arquivo teststock.py será criado durante este laboratório.

Criando Seu Primeiro Teste Unitário

O módulo unittest do Python é uma ferramenta poderosa que oferece uma maneira estruturada de organizar e executar testes. Antes de mergulharmos na escrita do nosso primeiro teste unitário, vamos entender alguns conceitos-chave. Test fixtures (estruturas de teste) são métodos como setUp e tearDown que ajudam a preparar o ambiente antes de um teste e limpá-lo depois. Test cases (casos de teste) são unidades individuais de teste, test suites (suítes de teste) são coleções de casos de teste, e test runners (executores de teste) são responsáveis por executar esses testes e apresentar os resultados.

Neste primeiro passo, vamos criar um arquivo de teste básico para a classe Stock, que já está definida no arquivo stock.py.

  1. Primeiro, vamos abrir o arquivo stock.py. Isso nos ajudará a entender a classe Stock que testaremos. Ao olhar o código em stock.py, podemos ver como a classe é estruturada, quais atributos ela tem e quais métodos ela fornece. Para visualizar o conteúdo do arquivo stock.py, execute o seguinte comando no seu terminal:
cat stock.py
  1. Agora, é hora de criar um novo arquivo chamado teststock.py usando seu editor de texto preferido. Este arquivo conterá nossos casos de teste para a classe Stock. Aqui está o código que você precisa escrever no arquivo teststock.py:
## teststock.py

import unittest
import stock

class TestStock(unittest.TestCase):
    def test_create(self):
        s = stock.Stock('GOOG', 100, 490.1)
        self.assertEqual(s.name, 'GOOG')
        self.assertEqual(s.shares, 100)
        self.assertEqual(s.price, 490.1)

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

Vamos detalhar os componentes-chave deste código:

  • import unittest: Esta linha importa o módulo unittest, que fornece as ferramentas e classes necessárias para escrever e executar testes em Python.
  • import stock: Isso importa o módulo que contém nossa classe Stock. Sem esta importação, não seríamos capazes de acessar a classe Stock em nosso código de teste.
  • class TestStock(unittest.TestCase): Criamos uma nova classe chamada TestStock que herda de unittest.TestCase. Isso torna nossa classe TestStock uma classe de caso de teste, que pode conter vários métodos de teste.
  • def test_create(self): Este é um método de teste. No framework unittest, todos os métodos de teste devem começar com o prefixo test_. Este método cria uma instância da classe Stock e, em seguida, usa o método assertEqual para verificar se os atributos da instância Stock correspondem aos valores esperados.
  • assertEqual: Este é um método fornecido pela classe TestCase. Ele verifica se dois valores são iguais. Se eles não forem iguais, o teste falhará.
  • unittest.main(): Quando este script é executado diretamente, unittest.main() executará todos os métodos de teste na classe TestStock e exibirá os resultados.
  1. Depois de escrever o código no arquivo teststock.py, salve-o. Em seguida, execute o seguinte comando no seu terminal para executar o teste:
python3 teststock.py

Você deve ver uma saída semelhante a esta:

.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

O ponto único (.) na saída indica que um teste passou com sucesso. Se um teste falhar, você verá um F em vez do ponto, juntamente com informações detalhadas sobre o que deu errado no teste. Esta saída ajuda você a identificar rapidamente se seu código está funcionando como esperado ou se há algum problema que precisa ser corrigido.

Expandindo Seus Casos de Teste

Agora que você criou um caso de teste básico, é hora de expandir seu escopo de teste. Adicionar mais testes ajudará você a cobrir a funcionalidade restante da classe Stock. Dessa forma, você pode garantir que todos os aspectos da classe funcionem como esperado. Vamos modificar a classe TestStock para incluir testes para vários métodos e propriedades.

  1. Abra o arquivo teststock.py. Dentro da classe TestStock, vamos adicionar alguns novos métodos de teste. Esses métodos testarão diferentes partes da classe Stock. Aqui está o código que você precisa adicionar:
def test_create_keyword_args(self):
    s = stock.Stock(name='GOOG', shares=100, price=490.1)
    self.assertEqual(s.name, 'GOOG')
    self.assertEqual(s.shares, 100)
    self.assertEqual(s.price, 490.1)

def test_cost(self):
    s = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(s.cost, 49010.0)

def test_sell(self):
    s = stock.Stock('GOOG', 100, 490.1)
    s.sell(20)
    self.assertEqual(s.shares, 80)

def test_from_row(self):
    row = ['GOOG', '100', '490.1']
    s = stock.Stock.from_row(row)
    self.assertEqual(s.name, 'GOOG')
    self.assertEqual(s.shares, 100)
    self.assertEqual(s.price, 490.1)

def test_repr(self):
    s = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)")

def test_eq(self):
    s1 = stock.Stock('GOOG', 100, 490.1)
    s2 = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(s1, s2)

Vamos dar uma olhada mais de perto no que cada um desses testes faz:

  • test_create_keyword_args: Este teste verifica se você pode criar um objeto Stock usando argumentos de palavra-chave. Ele verifica se os atributos do objeto são definidos corretamente.
  • test_cost: Este teste verifica se a propriedade cost de um objeto Stock retorna o valor correto, que é calculado como o número de ações multiplicado pelo preço.
  • test_sell: Este teste verifica se o método sell() de um objeto Stock atualiza corretamente o número de ações após a venda de algumas.
  • test_from_row: Este teste verifica se o método de classe from_row() pode criar uma nova instância Stock a partir de uma linha de dados.
  • test_repr: Este teste verifica se o método __repr__() de um objeto Stock retorna a representação de string esperada.
  • test_eq: Este teste verifica se o método __eq__() compara corretamente dois objetos Stock para ver se eles são iguais.
  1. Depois de adicionar esses métodos de teste, salve o arquivo teststock.py. Em seguida, execute os testes novamente usando o seguinte comando no seu terminal:
python3 teststock.py

Se todos os testes passarem, você deverá ver uma saída como esta:

......
----------------------------------------------------------------------
Ran 7 tests in 0.001s

OK

Os sete pontos na saída representam cada teste. Cada ponto indica que um teste passou com sucesso. Portanto, se você vir sete pontos, significa que todos os sete testes passaram.

Testando Exceções

Testar é uma parte crucial do desenvolvimento de software, e um aspecto importante disso é garantir que seu código possa lidar com condições de erro adequadamente. Em Python, o módulo unittest fornece uma maneira conveniente de testar se exceções específicas são levantadas conforme o esperado.

  1. Abra o arquivo teststock.py. Vamos adicionar alguns métodos de teste que são projetados para verificar exceções. Esses testes nos ajudarão a garantir que nosso código se comporte corretamente quando encontrar uma entrada inválida.
def test_shares_type(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(TypeError):
        s.shares = '50'

def test_shares_value(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(ValueError):
        s.shares = -50

def test_price_type(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(TypeError):
        s.price = '490.1'

def test_price_value(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(ValueError):
        s.price = -490.1

def test_attribute_error(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(AttributeError):
        s.share = 100  ## 'share' is incorrect, should be 'shares'

Agora, vamos entender como esses testes de exceção funcionam.

  • A instrução with self.assertRaises(ExceptionType): cria um gerenciador de contexto (context manager). Este gerenciador de contexto verifica se o código dentro do bloco with levanta a exceção especificada.
  • Se a exceção esperada for levantada dentro do bloco with, o teste passa. Isso significa que nosso código está detectando corretamente a entrada inválida e levantando o erro apropriado.
  • Se nenhuma exceção for levantada ou uma exceção diferente for levantada, o teste falha. Isso indica que nosso código pode não estar lidando com a entrada inválida conforme o esperado.

Esses testes são projetados para verificar os seguintes cenários:

  • Definir o atributo shares como uma string deve levantar um TypeError porque shares deve ser um número.
  • Definir o atributo shares como um número negativo deve levantar um ValueError, pois o número de ações não pode ser negativo.
  • Definir o atributo price como uma string deve levantar um TypeError porque price deve ser um número.
  • Definir o atributo price como um número negativo deve levantar um ValueError, pois o preço não pode ser negativo.
  • Tentar definir um atributo inexistente share (observe a falta de 's') deve levantar um AttributeError porque o nome correto do atributo é shares.
  1. Depois de adicionar esses métodos de teste, salve o arquivo teststock.py. Em seguida, execute todos os testes usando o seguinte comando no seu terminal:
python3 teststock.py

Se tudo estiver funcionando corretamente, você deverá ver uma saída indicando que todos os 12 testes foram aprovados. A saída terá esta aparência:

............
----------------------------------------------------------------------
Ran 12 tests in 0.002s

OK

Os doze pontos representam todos os testes que você escreveu até agora. Havia 7 testes da etapa anterior, e acabamos de adicionar 5 novos. Esta saída mostra que seu código está lidando com exceções conforme o esperado, o que é um ótimo sinal de um programa bem testado.

Executando Testes Selecionados e Usando Descoberta de Testes

O módulo unittest em Python é uma ferramenta poderosa que permite testar seu código de forma eficaz. Ele fornece várias maneiras de executar testes específicos ou descobrir e executar automaticamente todos os testes em seu projeto. Isso é muito útil porque ajuda você a se concentrar em partes específicas do seu código durante os testes ou verificar rapidamente o conjunto de testes de todo o projeto.

Executando Testes Específicos

Às vezes, você pode querer executar apenas métodos de teste ou classes de teste específicos em vez de todo o conjunto de testes. Você pode conseguir isso usando a opção de padrão com o módulo unittest. Isso oferece mais controle sobre quais testes são executados, o que pode ser útil ao depurar uma parte específica do seu código.

  1. Para executar apenas os testes relacionados à criação de um objeto Stock:
python3 -m unittest teststock.TestStock.test_create

Neste comando, python3 -m unittest diz ao Python para executar o módulo unittest. teststock é o nome do arquivo de teste, TestStock é o nome da classe de teste e test_create é o método de teste específico que queremos executar. Ao executar este comando, você pode verificar rapidamente se o código relacionado à criação de um objeto Stock está funcionando como esperado.

  1. Para executar todos os testes na classe TestStock:
python3 -m unittest teststock.TestStock

Aqui, omitimos o nome específico do método de teste. Portanto, este comando executará todos os métodos de teste dentro da classe TestStock no arquivo teststock. Isso é útil quando você deseja verificar a funcionalidade geral dos casos de teste do objeto Stock.

Usando Descoberta de Testes

O módulo unittest pode descobrir e executar automaticamente todos os arquivos de teste em seu projeto. Isso evita o trabalho de especificar manualmente cada arquivo de teste a ser executado, especialmente em projetos maiores com muitos arquivos de teste.

  1. Renomeie o arquivo atual para seguir o padrão de nomenclatura de descoberta de testes:
mv teststock.py test_stock.py

O mecanismo de descoberta de testes em unittest procura arquivos que seguem o padrão de nomenclatura test_*.py. Ao renomear o arquivo para test_stock.py, facilitamos para o módulo unittest encontrar e executar os testes neste arquivo.

  1. Execute a descoberta de testes:
python3 -m unittest discover

Este comando diz ao módulo unittest para descobrir e executar automaticamente todos os arquivos de teste que correspondem ao padrão test_*.py no diretório atual. Ele pesquisará no diretório e executará todos os casos de teste encontrados nos arquivos correspondentes.

  1. Você também pode especificar um diretório para pesquisar testes:
python3 -m unittest discover -s . -p "test_*.py"

Onde:

  • -s . especifica o diretório para iniciar a descoberta (diretório atual neste caso). O ponto (.) representa o diretório atual. Você pode alterar isso para outro caminho de diretório se quiser pesquisar testes em um local diferente.
  • -p "test_*.py" é o padrão para corresponder aos arquivos de teste. Isso garante que apenas arquivos com nomes começando com test_ e tendo a extensão .py sejam considerados como arquivos de teste.

Você deve ver todos os 12 testes serem executados e passarem, como antes.

  1. Renomeie o arquivo de volta para o nome original para consistência com o laboratório:
mv test_stock.py teststock.py

Após executar a descoberta de testes, renomeamos o arquivo de volta para seu nome original para manter o ambiente do laboratório consistente.

Ao usar a descoberta de testes, você pode facilmente executar todos os testes em um projeto sem ter que especificar cada arquivo de teste individualmente. Isso torna o processo de teste mais eficiente e menos propenso a erros.

Resumo

Neste laboratório, você aprendeu a usar o módulo unittest do Python para criar e executar testes automatizados. Você criou um caso de teste básico estendendo a classe unittest.TestCase, escreveu testes para verificar o funcionamento normal dos métodos e propriedades de uma classe e criou testes para verificar exceções apropriadas em condições de erro. Você também aprendeu a executar testes específicos e usar a descoberta de testes.

Testes unitários são uma habilidade fundamental no desenvolvimento de software, garantindo a confiabilidade e a correção do código. Escrever testes completos ajuda a detectar bugs precocemente e dá confiança no comportamento do seu código. Ao desenvolver aplicativos Python, considere adotar uma abordagem de desenvolvimento orientado a testes (TDD - Test-Driven Development), escrevendo testes antes de implementar a funcionalidade para um código mais robusto e sustentável.