Entender testes no Python com o módulo unittest

Concluído

O Python tem um módulo de teste chamado unittest em sua biblioteca padrão. Estar na biblioteca padrão significa que o módulo está incluído no próprio Python, portanto, não há necessidade de instalar nada para usá-lo.

É comum ver arquivos de teste que importam unittest e classes de teste que usam a biblioteca para implementar testes.

Classes e herança são a base para usar unittest para gravar testes. Portanto, não é possível gravar funções de teste ou outros testes que não usem a classe base do unittest.

Gravar testes usando unittest

Escrever testes com unittest exige a importação do módulo e a criação de pelo menos uma classe que herda da classe unittest.TestCase. Esta é a aparência de um teste de exemplo em um arquivo chamado test_assertions.py:

import unittest

class TestAssertions(unittest.TestCase):

    def test_equals(self):
        self.assertEqual("one string", "one string")
        
if __name__ == '__main__':
    unittest.main()

Há vários itens essenciais no arquivo que são necessários para que o teste funcione. De convenções de nomenclatura a métodos específicos que, quando combinados, permitem que um arquivo de teste seja executado.

Executar testes

Há duas maneiras de executar um arquivo de teste. Vejamos novamente o final do arquivo test_assertions.py, onde a chamada unittest.main() permite executar os testes executando o arquivo com Python:

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

A execução do teste é possível devido ao bloco if no final, onde a condição só é atendida ao executar o script diretamente com o Python. Vamos verificar a saída ao executar dessa maneira:

$ python test_assertions.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Outra maneira de executar testes com o módulo unittest é usando-o com o executável do Python. Desta vez, não é necessário especificar o nome do arquivo porque os testes podem ser detectados automaticamente:

$ python -m unittest
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Observação

A detecção de testes só é possível ao seguir as convenções de nomenclatura para métodos, classes e arquivos. Se o arquivo não iniciar com a palavra test, ele não será executado.

Convenções de nomenclatura

Os nomes de classe e método estão seguindo uma convenção de teste. A convenção é que eles precisam ser prefixados com test. Embora não seja obrigatório, as classes de teste usam camel-casing, e os métodos de teste são em letras minúsculas e as palavras são separadas por um sublinhado. Por exemplo, esta é a aparência de um teste para contas de clientes que verificam a criação e a exclusão:

class TestAccounts(unittest.TestCase):

    def test_creation(self):
        self.assertTrue(account.create())

    def test_deletion(self):
        self.assertTrue(account.delete())

Classes de teste ou métodos que não seguem essas convenções não serão executados. Embora possa parecer um problema não executar todos os métodos em uma classe, isso pode ser útil ao criar código que não seja de teste.

Asserções e métodos de asserção

É essencial usar métodos de asserção em vez da função assert() interna do Python para ter relatórios avançados quando ocorrerem falhas. O exemplo test_assertion.py usa self.assertEqual(), um dos muitos métodos especiais da classe unittest.TestCase para garantir que dois valores sejam iguais:

self.assertEqual("one string", "one string")

Nesse caso, ambas as cadeias de caracteres são iguais, portanto, o teste é aprovado. Testar a igualdade é uma das muitas asserções diferentes que a classe unittest.TestCase oferece. Embora existam mais de 30 métodos de asserção, os seguintes são mais comumente usados, além de self.assertEqual():

  • self.assertTrue(value): verifique se value é true.
  • self.assertFalse(value): verifique se value é false.
  • self.assertNotEqual(a, b): confirme que a e b não são iguais.

Observação

Estruturas de dados do Python, como dicionários e listas, são avaliadas como true quando têm pelo menos um item neles e false quando estão vazias. Evite avaliar estruturas de dados dessa maneira com assertTrue() e assertFalse() para evitar que itens inesperados não sejam detectados. É preferível ser o mais preciso possível ao fazer testes.

Falhas e relatórios

A aprovação de testes é uma ótima maneira de garantir a robustez. Ainda assim, entender o relatório de falhas é crucial para atualizar e corrigir o código de produção.

No próximo exemplo, altero uma das cadeias de caracteres de "one string" para "other string". Em seguida, executo o teste com o Python. Veja a aparência da saída agora:

$ python test_assertions.py
F
======================================================================
FAIL: test_equals (__main__.TestAssertions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/private/tmp/test_assertions.py", line 6, in test_equals
    self.assertEqual("one string", "other string")
AssertionError: 'one string' != 'other string'
- one string
?  ^
+ other string
?  ^^ +


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

FAILED (failures=1)

O teste mostra uma falha de asserção porque as cadeias de caracteres são diferentes. O relatório aprimora o erro de asserção para mostrar que as cadeias de caracteres são incompatíveis em alguns caracteres. É útil verificar onde exatamente ocorreu o erro, pois restringe a área onde está o problema. Nesse caso, ele está no arquivo test_assertions.py na linha seis.