Tutorial: Testes Unitários e Python — Parte I

Jorge Modesto Neto
mercos-engineering
Published in
4 min readOct 2, 2016

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_*’:

Criando classe de testes

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:

Método de teste alterado

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’:

Asserção alterada

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.001s
FAILED (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:

Criando função

Alteramos o arquivo de testes para importar a função:

Adicionando import da função no arquivo de testes

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.000s
FAILED (errors=1)

Então vamos obedecer os testes:

Adicionando parâmetro no método

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.000s
FAILED (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:

Adicionando retorno na função

Vamos executar os testes:

.
--------------------------------------------------------------------
Ran 1 test in 0.000s
OK

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:

Criando segundo método de testes

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.000s
FAILED (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:

Iterando sobre a solução

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.000s
FAILED (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:

Comparando com frase em lowercase

Executamos novamente:

..
--------------------------------------------------------------------
Ran 2 tests in 0.000s
OK

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.

--

--