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.

<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" />
			{% csrf_token %}
		</form>
		<table id="id_list_table">
		</table>
	</body>
</html>

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.

(superlists) tdd@mlp:~/superlists/superlists$ python functional_tests.py 
F
======================================================================
FAIL: 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 55, in test_can_start_a_list_and_retrieve_it_later
    self.assertTrue(
AssertionError: False is not true

----------------------------------------------------------------------
Ran 1 test in 5.076s

FAILED (failures=1)

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:

from django.urls import resolve
from django.test import TestCase
from lists.views import home_page

class HomePageTest(TestCase):

	def test_root_url_resolves_to_home_page_view(self):
		found = resolve('/')
		self.assertEquals(found.func, home_page)

	def test_home_page_returns_correct_html(self):
		response = self.client.get('/')
		self.assertTemplateUsed(response, 'home.html')

	def test_can_save_a_POST_request(self):
		response = self.client.post('/', data={'item_text': 'A new list item'})
		self.assertIn('A new list item', response.content.decode())

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).

(superlists) tdd@mlp:~/superlists/superlists$ python manage.py test
Found 3 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F..
======================================================================
FAIL: test_can_save_a_POST_request (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tdd/superlists/superlists/lists/tests.py", line 17, in test_can_save_a_POST_request
    self.assertIn('A new list item', response.content.decode())
AssertionError: 'A new list item' not found in '<html>\n\t<head>\n\t\t<title>To-Do lists</title>\n\t</head>\n\t<body>\n\t\t<h1>Your To-Do list</h1>\n\t\t<form method="POST">\n\t\t\t<input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />\n\t\t\t<input type="hidden" name="csrfmiddlewaretoken" value="n680kiOXpueVd6KpeHXOxrkzAoH2HxLSAO6Y2xJTXVsekFlqlSOkTYbbYMChks7v">\n\t\t</form>\n\t\t<table id="id_list_table">\n\t\t</table>\n\t</body>\n</html>'

----------------------------------------------------------------------
Ran 3 tests in 0.012s

FAILED (failures=1)
Destroying test database for alias 'default'...

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.

from django.http import HttpResponse
from django.shortcuts import render

# Create your views here.
def home_page(request):
	if (request.method == 'POST'):
		return HttpResponse(request.POST['item_text'])
	return render(request, 'home.html')

Ao reexecutar os testes temos o resultado de sucesso ilustrado abaixo:

(superlists) tdd@mlp:~/superlists/superlists$ python manage.py test
Found 3 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.013s

OK
Destroying test database for alias 'default'...

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).

<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" />
			{% csrf_token %}
		</form>
		<table id="id_list_table">
			<tr><td>{{ new_item_text }}</td></tr>
		</table>
	</body>
</html>

Vamos alterar o nosso teste unitário (lists/tests.py) para verificar o uso do nosso template alterado.

from django.urls import resolve
from django.test import TestCase
from lists.views import home_page

class HomePageTest(TestCase):

	def test_root_url_resolves_to_home_page_view(self):
		found = resolve('/')
		self.assertEquals(found.func, home_page)

	def test_home_page_returns_correct_html(self):
		response = self.client.get('/')
		self.assertTemplateUsed(response, 'home.html')

	def test_can_save_a_POST_request(self):
		response = self.client.post('/', data={'item_text': 'A new list item'})
		self.assertIn('A new list item', response.content.decode())
		self.assertTemplateUsed(response, 'home.html')

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.

(superlists) tdd@mlp:~/superlists/superlists$ python manage.py test
Found 3 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F..
======================================================================
FAIL: test_can_save_a_POST_request (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tdd/superlists/superlists/lists/tests.py", line 18, in test_can_save_a_POST_request
    self.assertTemplateUsed(response, 'home.html')
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/test/testcases.py", line 712, in assertTemplateUsed
    self._assert_template_used(template_name, template_names, msg_prefix, count)
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/test/testcases.py", line 676, in _assert_template_used
    self.fail(msg_prefix + "No templates used to render the response")
AssertionError: No templates used to render the response

----------------------------------------------------------------------
Ran 3 tests in 0.024s

FAILED (failures=1)
Destroying test database for alias 'default'...

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.

from django.shortcuts import render

# Create your views here.
def home_page(request):
	return render(request, 'home.html', {'new_item_text': request.POST['item_text']} )

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).

(superlists) tdd@mlp:~/superlists/superlists$ python manage.py test
Found 3 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.E.
======================================================================
ERROR: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/utils/datastructures.py", line 84, in __getitem__
    list_ = super().__getitem__(key)
KeyError: 'item_text'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/tdd/superlists/superlists/lists/tests.py", line 12, in test_home_page_returns_correct_html
    response = self.client.get('/')
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/test/client.py", line 1049, in get
    response = super().get(path, data=data, secure=secure, headers=headers, **extra)
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/test/client.py", line 465, in get
    return self.generic(
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/test/client.py", line 617, in generic
    return self.request(**r)
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/test/client.py", line 1013, in request
    self.check_exception(response)
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/test/client.py", line 743, in check_exception
    raise exc_value
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/tdd/superlists/superlists/lists/views.py", line 5, in home_page
    return render(request, 'home.html', {'new_item_text': request.POST['item_text']} )
  File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/django/utils/datastructures.py", line 86, in __getitem__
    raise MultiValueDictKeyError(key)
django.utils.datastructures.MultiValueDictKeyError: 'item_text'

----------------------------------------------------------------------
Ran 3 tests in 0.083s

FAILED (errors=1)
Destroying test database for alias 'default'...

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 ('').

from django.shortcuts import render

# Create your views here.
def home_page(request):
	return render(request, 'home.html', {
		'new_item_text': request.POST.get('item_text', '')
	})

Ao executar os testes unitário novamente após a alteração os mesmos passam com sucesso, conforme mensagem abaixo:

(superlists) tdd@mlp:~/superlists/superlists$ python manage.py test
Found 3 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.016s

OK
Destroying test database for alias 'default'...

Entretanto, ao reexecutar os testes funcionais obtemos uma falha:

(superlists) tdd@mlp:~/superlists/superlists$ python functional_tests.py 
F
======================================================================
FAIL: 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 55, in test_can_start_a_list_and_retrieve_it_later
    self.assertTrue(
AssertionError: False is not true

----------------------------------------------------------------------
Ran 1 test in 5.352s

FAILED (failures=1)

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.

...
		self.assertTrue(
			any(row.text == '1: Buy peacock feathers' for row in rows),
			f"New to-do item not appear in table. Content were:\n{table.text}"
		)
...

Após a alteração e reexecução dos testes funcionais, a mensagem de falha aparece como abaixo (linhas 9 e 10):

(superlists) tdd@mlp:~/superlists/superlists$ python functional_tests.py 
F
======================================================================
FAIL: 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 55, in test_can_start_a_list_and_retrieve_it_later
    self.assertTrue(
AssertionError: False is not true : New to-do item not appear in table. Content were:
Buy peacock feathers

----------------------------------------------------------------------
Ran 1 test in 5.073s

FAILED (failures=1)

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.

...
self.assertIn('1: Buy peacock feathers', [row.text for row in rows])
...

Após a troca do assert, o resultado da execução fica assim (linha 9):

(superlists) tdd@mlp:~/superlists/superlists$ python functional_tests.py 
F
======================================================================
FAIL: 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 55, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('1: Buy peacock feathers', [row.text for row in rows])
AssertionError: '1: Buy peacock feathers' not found in ['Buy peacock feathers']

----------------------------------------------------------------------
Ran 1 test in 5.412s

FAILED (failures=1)

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).

<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" />
			{% csrf_token %}
		</form>
		<table id="id_list_table">
			<tr><td>1: {{ new_item_text }}</td></tr>
		</table>
	</body>
</html>

E agora ao reexecutar os testes funcionais conseguimos avançar e parar o self.fail que incluimos propositalmente em nosso teste.

(superlists) tdd@mlp:~/superlists/superlists$ python functional_tests.py 
F
======================================================================
FAIL: 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 62, in test_can_start_a_list_and_retrieve_it_later
    self.fail('Finish the test!')
AssertionError: Finish the test!

----------------------------------------------------------------------
Ran 1 test in 5.160s

FAILED (failures=1)

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:

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

import time
import unittest

class NewVisitorTest(unittest.TestCase):

	def setUp(self):
		self.browser = webdriver.Firefox()

	def tearDown(self):
		self.browser.quit()

	def test_can_start_a_list_and_retrieve_it_later(self):
		# Edith ouviu falar de uma nova aplicação online interessante
		# para lista de tarefas. Ela decide verificar a homepage

		self.browser.get("http://localhost:8000")

		# Ela percebe que o título da página e o cabeçalho mencionam
		# listas de tarefas (to-do)

		self.assertIn('To-Do', self.browser.title)
		header_text = self.browser.find_element(By.TAG_NAME, 'h1').text
		self.assertIn('To-Do', header_text)
		
		# Ela é convidada a inserir um item de tarefa imediatamente

		
		inputbox = self.browser.find_element(By.ID, 'id_new_item')
		self.assertEqual(
			inputbox.get_attribute('placeholder'),
			'Enter a to-do item'
		)

		# Ela digita "Buy peacock feathers" (Comprar penas de pavão)
		# em uma nova caixa de texto (o hobby de Edith é fazer iscas
		# para pesca com fly)

		inputbox.send_keys('Buy peacock feathers')


		# Quando ela tecla enter, a página é atualizada, e agora
		# a página lista "1 - Buy peacock feathers" como um item em 
		# uma lista de tarefas

		inputbox.send_keys(Keys.ENTER)
		time.sleep(1)

		table = self.browser.find_element(By.ID,'id_list_table')
		rows = table.find_elements(By.TAG_NAME, 'tr')
		self.assertIn('1: Buy peacock feathers', [row.text for row in rows])

		# Ainda continua havendo uma caixa de texto convidando-a a 
		# acrescentar outro item. Ela insere "Use peacock feathers 
		# make a fly" (Usar penas de pavão para fazer um fly - 
		# Edith é bem metódica)
		inputbox = self.browser.find_element(By.ID,'id_new_item')
		inputbox.send_keys("Use peacock feathers to make a fly")
		inputbox.send_keys(Keys.ENTER)
		time.sleep(1)

		# A página é atualizada novamente e agora mostra os dois
		# itens em sua lista
		table = self.browser.find_element(By.ID,'id_list_table')
		rows = table.find_elements(By.TAG_NAME,'tr')
		self.assertIn('1: Buy peacock feathers', [row.text for row in rows])
		self.assertIn('2: Use peacock feathers to make a fly', 
			[row.text for row in rows])

		# Edith se pergunta se o site lembrará de sua lista. Então
		# ela nota que o site gerou um URL único para ela -- há um 
		# pequeno texto explicativo para isso.

		self.fail('Finish the test!')
		
		# Ela acessa essa URL -- sua lista de tarefas continua lá.

		# Satisfeita, ela volta a dormir

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

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.

(superlists) tdd@mlp:~/superlists/superlists$ python functional_tests.py 
F
======================================================================
FAIL: 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 69, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('1: Buy peacock feathers', [row.text for row in rows])
AssertionError: '1: Buy peacock feathers' not found in ['1: Use peacock feathers to make a fly']

----------------------------------------------------------------------
Ran 1 test in 6.212s

FAILED (failures=1)

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.,

(superlists) tdd@mlp:~/superlists/superlists$ git commit -am "Estending user story to deal with a second list item"
[master 24e8605] Estending user story to deal with a second list item
 2 files changed, 14 insertions(+), 8 deletions(-)
(superlists) auri@av:~/superlists/superlists$ git push
Username for 'https://github.com': aurimrv
Password for 'https://aurimrv@github.com': 
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 12 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 693 bytes | 693.00 KiB/s, done.
Total 6 (delta 4), reused 0 (delta 0)
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.
To https://github.com/aurimrv/superlists.git
   d0698d2..24e8605  master -> master

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.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

import time
import unittest

class NewVisitorTest(unittest.TestCase):

	def setUp(self):
		self.browser = webdriver.Firefox()

	def tearDown(self):
		self.browser.quit()

	# Auxiliary method 
	def check_for_row_in_list_table(self, row_text):
		table = self.browser.find_element(By.ID,'id_list_table')
		rows = table.find_elements(By.TAG_NAME, 'tr')
		self.assertIn(row_text, [row.text for row in rows])

	def test_can_start_a_list_and_retrieve_it_later(self):
		# Edith ouviu falar de uma nova aplicação online interessante
		# para lista de tarefas. Ela decide verificar a homepage

		self.browser.get("http://localhost:8000")

		# Ela percebe que o título da página e o cabeçalho mencionam
		# listas de tarefas (to-do)

		self.assertIn('To-Do', self.browser.title)
		header_text = self.browser.find_element(By.TAG_NAME, 'h1').text
		self.assertIn('To-Do', header_text)
		
		# Ela é convidada a inserir um item de tarefa imediatamente

		inputbox = self.browser.find_element(By.ID, 'id_new_item')
		self.assertEqual(
			inputbox.get_attribute('placeholder'),
			'Enter a to-do item'
		)

		# Ela digita "Buy peacock feathers" (Comprar penas de pavão)
		# em uma nova caixa de texto (o hobby de Edith é fazer iscas
		# para pesca com fly)

		inputbox.send_keys('Buy peacock feathers')


		# Quando ela tecla enter, a página é atualizada, e agora
		# a página lista "1 - Buy peacock feathers" como um item em 
		# uma lista de tarefas

		inputbox.send_keys(Keys.ENTER)
		time.sleep(1)
		self.check_for_row_in_list_table('1: Buy peacock feathers')

		# Ainda continua havendo uma caixa de texto convidando-a a 
		# acrescentar outro item. Ela insere "Use peacock feathers 
		# make a fly" (Usar penas de pavão para fazer um fly - 
		# Edith é bem metódica)
		inputbox = self.browser.find_element(By.ID,'id_new_item')
		inputbox.send_keys("Use peacock feathers to make a fly")
		inputbox.send_keys(Keys.ENTER)
		time.sleep(1)

		# A página é atualizada novamente e agora mostra os dois
		# itens em sua lista
		self.check_for_row_in_list_table('1: Buy peacock feathers')
		self.check_for_row_in_list_table('2: Use peacock feathers to make a fly')

		# Edith se pergunta se o site lembrará de sua lista. Então
		# ela nota que o site gerou um URL único para ela -- há um 
		# pequeno texto explicativo para isso.

		self.fail('Finish the test!')

		# Ela acessa essa URL -- sua lista de tarefas continua lá.

		# Satisfeita, ela volta a dormir

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

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.

(superlists) tdd@mlp:~/superlists/superlists$ git commit -am "Refactoring the functional testing to eliminate redundancy"
[master eef7e77] Refactoring the functional testing to eliminate redundancy
 1 file changed, 9 insertions(+), 10 deletions(-)
(superlists) auri@av:~/superlists/superlists$ git push
Username for 'https://github.com': aurimrv
Password for 'https://aurimrv@github.com': 
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 452 bytes | 452.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/aurimrv/superlists.git
   24e8605..eef7e77  master -> master

Last updated