Tutorial: Testes Unitários e Python — Parte I
Se você não sabe por onde começar, comece testando.
Esta primeira parte do tutorial foi criada para pessoas que nunca tiveram contato com testes unitários na prática, será apresentada a criação e execução de testes em Python e o básico do fluxo de desenvolvimento com a metodologia Test Driven Development (TDD).
Código Python puro pode ser facilmente testado com a biblioteca embutida unittest. Nosso objetivo será criar uma simples função que retorna se uma frase é um Pangrama ou não, ou seja, uma frase que tem todas as letras do alfabeto. Começaremos com os testes.
Crie um arquivo que irá conter os testes com o prefixo ‘test_*’.
$ touch test_verificar_pangrama.py
Neste arquivo, criaremos um caso de testes com uma classe que herda de unittest.TestCase, onde todos os métodos de teste devem ter como prefixo ‘test_*’:
Vamos organizar o método de teste usando o padrão Arrange-Act-Assert, onde primeiro se define as pré-condições e inputs, depois agimos no método sob teste e então fazemos a asserção de que é o resultado esperado.
Precisamos definir então o comportamento da função a ser testada, ou seja, o input e output esperado. No caso da nossa função, definiremos que o input é uma string e o output deverá ser um boolean indicando se é ou não um pangrama. Para o teste positivo vamos usar o pangrama:
Zebras caolhas de Java querem mandar fax para moça gigante de New York
Alteramos então a função do teste existente para que tenha as seguintes características:
O método assertEqual é um dos helpers que a classe TestCase nos fornece. O primeiro argumento do método é o resultado esperado e o segundo é o próprio resultado.
Podemos substituir o ‘assertEqual’ por um método de asserção mais apropriado que também é oferecido pela classe ‘TestCase’:
Este já espera que o argumento seja verdadeiro, então não precisamos de outro argumento para definir qual o retorno esperado.
Agora temos um teste criado que pode ser executado com o seguinte comando:
$ python -m unittest test_verificar_pangrama
Executando este comando teremos a seguinte mensagem:
E
====================================================================
ERROR: test_retorna_verdadeiro_quando_frase_pangrama (test_verificar_pangrama.VerificarPangramaTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "test_verificar_pangrama.py", line 8, in test_retorna_verdadeiro_quando_frase_pangrama
frase_eh_pangrama = verificar_pangrama(frase)
NameError: global name 'verificar_pangrama' is not defined--------------------------------------------------------------------
Ran 1 test in 0.001sFAILED (errors=1)
Conseguiu identificar o problema? O problema é que obviamente ainda não temos a nossa função nem a importamos. Vamos fazer tudo em pequenos passos e deixar que os testes nos digam o que fazer. Criemos então o arquivo que irá conter a função:
$ touch verificar_pangrama.py
Dentro deste, definimos a nossa função:
Alteramos o arquivo de testes para importar a função:
Execute o teste novamente, ele vai nos mostrar que precisamos de uma função que receba um argumento:
E
====================================================================
ERROR: test_retorna_verdadeiro_quando_frase_pangrama (test_verificar_pangrama.VerificarPangramaTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "test_verificar_pangrama.py", line 9, in test_retorna_verdadeiro_quando_frase_pangrama
frase_eh_pangrama = verificar_pangrama(frase)
TypeError: verificar_pangrama() takes no arguments (1 given)--------------------------------------------------------------------
Ran 1 test in 0.000sFAILED (errors=1)
Então vamos obedecer os testes:
Executando os testes novamente temos o seguinte resultado:
F
====================================================================
FAIL: test_retorna_verdadeiro_quando_frase_pangrama (test_verificar_pangrama.VerificarPangramaTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "test_verificar_pangrama.py", line 11, in
test_retorna_verdadeiro_quando_frase_pangrama
self.assertTrue(frase_eh_pangrama)
AssertionError: False is not true--------------------------------------------------------------------
Ran 1 test in 0.000sFAILED (failures=1)
O que mudou do estado anterior para este? O teste agora se apresenta como FAIL e não mais como ERROR, já que agora o problema está na asserção, ou seja, no comportamento do método, e não em erros de semântica etc.
Nosso teste agora espera que o retorno seja True, então daremos o que o teste quer alterando a nossa função:
Vamos executar os testes:
.
--------------------------------------------------------------------
Ran 1 test in 0.000sOK
OK, agora os testes passam, mas você provavelmente acredita que a função ainda não tem o comportamento correto. Você está certo, mas como vamos descobrir se ela se comporta do jeito certo? Se disse “com mais testes”, acertou. Além do método que já existe na nossa classe de testes vamos criar outro:
E agora executando os testes:
F.
====================================================================
FAIL: test_retorna_falso_quando_frase_nao_eh_pangrama (test_verificar_pangrama.VerificarPangramaTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "test_verificar_pangrama.py", line 19, in test_retorna_falso_quando_frase_nao_eh_pangrama
self.assertFalse(frase_eh_pangrama)
AssertionError: True is not false--------------------------------------------------------------------
Ran 2 tests in 0.000sFAILED (failures=1)
Maravilha, podemos agora refatorar a função para obedecer aos nossos testes. Uma frase pangrama é uma frase que contém todas as letras do alfabeto, então uma solução possível é utilizarmos a lista com o alfabeto em letras minúsculas que existe no pacote string do python e verificar se todas as letras estão na frase:
Executamos nossos testes e:
.F
====================================================================
FAIL: test_retorna_verdadeiro_quando_frase_pangrama (test_verificar_pangrama.VerificarPangramaTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "test_verificar_pangrama.py", line 12, in test_retorna_verdadeiro_quando_frase_pangrama
self.assertTrue(frase_eh_pangrama)
AssertionError: False is not true--------------------------------------------------------------------
Ran 2 tests in 0.000sFAILED (failures=1)
O primeiro teste test_retorna_verdadeiro_quando_frase_pangrama retornou falso. Você consegue descobrir qual foi o erro de lógica cometido? Estamos comparando o alfabeto em minúsculo com a frase, que pode ter letras maiúsculas. Um pequeno ajuste é necessário:
Executamos novamente:
..
--------------------------------------------------------------------
Ran 2 tests in 0.000sOK
Pronto, temos uma função que verifica se uma frase é um pangrama ou não coberta por testes e criada com TDD :). Sinta-se livre para progredir em seu desenvolvimento, criar outras validações e mais testes (ainda há espaço para mais).
Conclusão
Testes ajudam, mas não fazem milagres. Eles não te dirão exatamente o que fazer em todos os momentos, mas é uma prática que mantém o código saudável e um sistema sólido.