4.1 Envio e Processamento de Requisição POST
No capítulo anterior, interrompemos a implementação da nossa aplicação pela necessidade de processarmos os itens a serem armazenados em nossa lista. O primeiro passo para isso é permitir que nossa página envie uma requisição POST para ser processada.
Para fazer isso precisamos realizar duas operações no nosso template: 1) inserir um atributo name no elemento input(name="item_text") (linha 8); e 2) encapsular o elemento input em uma tag form com método POST (linhas 7 a 9). O resultado é mostrado abaixo:
<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>Your To-Do list</h1>
<form method="POST">
<input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />
</form>
<table id="id_list_table">
</table>
</body>
</html>Com essa alteração, ao executar o teste funcional temos o resultado abaixo:
(superlists) tdd@mlp:~/superlists/superlists$ python functional_tests.py
E
======================================================================
ERROR: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/tdd/superlists/superlists/functional_tests.py", line 53, in test_can_start_a_list_and_retrieve_it_later
table = self.browser.find_element(By.ID,'id_list_table')
File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 748, in find_element
return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"]
File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 354, in execute
self.error_handler.check_response(response)
File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/selenium/webdriver/remote/errorhandler.py", line 229, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [id="id_list_table"]; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
Stacktrace:
RemoteError@chrome://remote/content/shared/RemoteError.sys.mjs:8:8
WebDriverError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:193:5
NoSuchElementError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:511:5
dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16
----------------------------------------------------------------------
Ran 1 test in 4.890s
FAILED (errors=1)Pela mensagem, o Selenium não foi capaz de localizar na página o elemento com id="id_list_table" (linhas 7 e 8). Quando um elemento não é encontrado, a primeira coisa que imaginamos é que ainda não deu tempo do elemento ser renderizado e, desse modo, podemos tentar aumentar o tempo do comando time.sleep e verificar o que acontece. No caso, substituímos o antigo time.sleep(1) por time.sleep(10) no nosso caso de teste funcional (functional_tests.py). Agora, ao executarmos os testes, a espera será bem maior e será possível verificarmos se o Selenium é capaz de achar o elemento procurado. Entretanto, o que vemos é que o problema persiste mas, com o tempo maior conseguimos ler a tela e a mensagem de erro que ela apresenta, exibida abaixo.

Basicamente, o Django está nos alertando que, quando utilizamos métodos POST em formulário, é necessária a utilização de um token para proteger nossa aplicação contra o ataque de Cross Site Request Forgery (CSRF). Para mais informações o leitor interessado pode consultar a documentação do Django em https://docs.djangoproject.com/en/3.2/ref/csrf/. O procedimento básico para resolver o problema no nosso exemplo é incluir a tag {% csrf_token %}dentro de todo elemento <form> que for para uma URL interna. No Django, tags envolvidas por {% ... %} são chamadas de tags de template.
Assim sendo, a correção do nosso template é apresentada abaixo.
Agora, ao executarmos os testes, apesar de demorar um tempo, o resultado de falha é o mesmo que estávamos obtendo anteriormente e podemos retornar o comando time.sleep(10) para time.sleep(1) em nosso caso de teste funcional (functional_tests.py), já que o problema não era de sincronização.
Processando uma Requisição POST no Servidor
Como podemos observar no nosso form de submissão de dados via método POST, o mesmo não contém um atributo action e, dessa forma, a submissão dos dados é feita para a mesma URL, ou seja, para a raiz de nosso site ('/'), sendo nossa função home_page responsável por atender essa requisição. Desse modo, para darmos andamento na evolução da aplicação precisamos de um novo teste unitário para nos guiar nessa missão. Para isso, acrescentaremos um novo método de teste (test_can_save_a_POST_request- linhas 15 a 17) no nosso arquivo lists/tests.py, conforme ilustrado abaixo:
Utilizamos o método self.client.post (linha 16) no caso de teste para enviar uma requisição POST e passamos no argumento data as informações que desejamos que sejam enviadas.
Ao executar o teste obtemos o resultado abaixo, indicando que o item 'A new list item' não foi encontrado (linha 12).
A correção da aplicação para fazer o teste passar pode ser a inclusão de um simples if no arquivo lists/view.py com um tratamento mínimo, conforme abaixo. Obviamente essa ainda não é a solução que desejamos, com certeza, mas lembre-se que estamos seguindo a risca o que prega o TDD.
Ao reexecutar os testes temos o resultado de sucesso ilustrado abaixo:
Passando Variáveis do Python para o Template
A forma de integrarmos variáveis do Python dentro de templates Django é por meio da notação {{...}}. O objeto presente dentro desses pares de chaves serão convertidos para uma string dentro de nosso template. Desse modo, podemos iniciar as modificações no código de nosso template para aceitar variáveis Python. O exemplo a seguir ilustra o uso da variável {{ new_item_text }} dentro de nosso template do arquivo home.html (linha 12).
Vamos alterar o nosso teste unitário (lists/tests.py) para verificar o uso do nosso template alterado.
Ao executar o teste temos o resultado abaixo, ou seja, uma falha pois nosso código com aquela implementação simples não está mais conseguindo enganar nosso teste e, desse modo, podemos melhorar nossa view.
A correção envolve usarmos o template e passarmos os dados que desejamos para ele, conforme ilustrado abaixo na alteração do arquivo lists/view.py. A função render que estamos usando aceita um terceiro parâmetro que consiste de um dicionário de dados que faz o mapeamento de nomes de variáveis do template para os seus respectivos valores.
Após a alteração, ao reexecutar o teste, passamos a ter a chamada "falha esperada" mas de um modo diferente do que havíamos presenciado até o momento. O teste que estávamos trabalhando passou e o que falhou foi o teste anterior que estava funcionando (observe a linha 5 abaixo).
A correção do código para fazer o teste passar é usarmos a função dict.get do Python para obter os valores a serem passados. O código abaixo mostra a correção. Para saber mais sobre o dict.get podemos consultar a documentação oficial em https://docs.python.org/3/library/stdtypes.html#dict. Basicamente, o método get retorna o valor da chave se a chave for encontrada no dicionário, do contrário, ela retorna o valor padrão que, no nosso exemplo, é o string vazio ('').
Ao executar os testes unitário novamente após a alteração os mesmos passam com sucesso, conforme mensagem abaixo:
Entretanto, ao reexecutar os testes funcionais obtemos uma falha:
Existe uma forma de melhorarmos um pouco mais essa mensagem de erro exibida. O Python oferece na versão 3.6 em diante o recurso chamado de f-string que permite presceder uma string com a letra f e então fazer uso das chaves para inserir variáveis locais na string. Observe como ficou o trecho de código dos testes funcionais (functional_tests.py) com o uso do recurso de f-string.
Após a alteração e reexecução dos testes funcionais, a mensagem de falha aparece como abaixo (linhas 9 e 10):
Entretanto, conseguimos melhorar ainda mais nosso caso de teste e substituir o assertTrue por um assertIn e, gratuitamente, ganharmos a mensagens de erro sem fazer uso do f-string.
Após a troca do assert, o resultado da execução fica assim (linha 9):
Como podemos observar a diferença entre o resultado obtido e o esperado é que o teste funcional deseja que o resultado seja enumerado e o retorno ainda não está enumerando os itens retornados. Para conseguirmos ter o teste aprovado podemos fazer essa simples alteração no template (linha 12).
E agora ao reexecutar os testes funcionais conseguimos avançar e parar o self.fail que incluimos propositalmente em nosso teste.
Podemos então estender mais um pouco o nosso teste funcional e o fizemos complementando as descrições da história entre as linhas 60 a 77. Veja código completo abaixo:
O problema é que com essa complementação, nossos testes funcionais voltam a falhar uma vez que o código da aplicação não envolve toda a lista de itens enumerada. Na verdade, sequer essa lista é salva para ser recuperada posteriormente.
Antes de continuarmos e alterarmos o código da aplicação, podemos melhorar um pouco mais nosso código funcional removendo a redundância presente nele. Primeiro vamos confirmar essas alterações realizadas até aqui e, em seguida, melhoraremos o nosso caso de teste.,
Se observarmos, estamos fazendo uma verificação dos itens da lista mais de uma vez e isso gera redundância no código de testes. Podemos melhorá-lo conforme abaixo.
Observe que criamos o método auxiliar check_for_row_in_list_table (linhas 16 a 20) e removemos a redundância fazendo a chamada a esse método nas linhas 56, 69 e 70. Refatorado nosso caso de teste, vamos confirmar novamente nossas mudanças colocando o código sob controle de versão.
Last updated
Was this helpful?