Na seção anterior finalizamos completando toda uma parte da história descrita no teste funcional. É hora de melhorá-lo pois a execução dos testes parou exatamente na mensagem que havíamos deixado nos testes nos lembrando de que o teste ainda não está completo.
Entretanto, antes de complementar o teste funcional, Percival (2017) nos apresenta diversos questionamentos que devem estar surgindo sobre a viabilidade do TDD. Algumas das questões que ele argumenta que as pessoas iniciando com TDD fazem são: "Será mesmo que isso vale a pena? Todos esses testes não são excessivos? Será que não estão duplicados? Esses testes não são muito triviais? Isso é mesmo utilizado na prática?". Ele comenta que ele mesmo se fez essas perguntas e que é preciso dedicação e persistência para compreender e colher os benefícios.
"O TDD é uma disciplina, e isso significa que não é algo que surge naturalmente; como muitas das compensações não são imediatas, mas só aparecem no longo prazo, você precisará se esforçar para segui-lo no momento." (Percival, 2017)
Então vamos em frente e vamos nos esforçar para dar nosso melhor nessa tarefa de usar testes no desenvolvimento de um produto de software.
O próximo passo então é estendermos o nosso teste funcional para avançar na história nele descrita. O trecho de código a seguir ilustra como complementamos algumas frases da história, além do ponto que continha o comando self.fail('Finish the test!') (linha 22 do arquivo functional_tests.py). Vide resultado da execução no final da Seção 3.5.1. A seguir, o comando que estava na linha 22 foi movido para a linha 60, e novos comandos foram inseridos para atender a outras funcionalidades demandadas pelo usuário.
from selenium import webdriverfrom selenium.webdriver.common.keys import Keysfrom selenium.webdriver.common.by import Byimport timeimport unittestclassNewVisitorTest(unittest.TestCase):defsetUp(self): self.browser = webdriver.Firefox()deftearDown(self): self.browser.quit()deftest_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 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.assertTrue(any(row.text =='1: Buy peacock feathers'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) self.fail('Finish the test!')# A página é atualizada novamente e agora mostra os dois# itens em sua lista# 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.# Ela acessa essa URL -- sua lista de tarefas continua lá.# Satisfeita, ela volta a dormirif__name__=='__main__': unittest.main()
Como pode ser observado acima, incluímos vários comandos novos no código de teste funcional. São, em sua maioria, métodos do Selenium, como o método find_element, responsável por localizar elementos na página web conforme parâmetro foenecido (By.TAG_NANE ou By.ID), ou send_keys, responsável por enviar dados para campos de formulário em páginas web.
A execução do teste acima apresenta, conforme esperado, uma falha, com a mensagem na linha 15 abaixo, ou seja, a execução do teste não foi capaz de localizar um componente h1 na página.
(superlists) tdd@mlp:~/superlists/superlists$pythonfunctional_tests.pyE======================================================================ERROR:test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)----------------------------------------------------------------------Traceback (most recentcalllast):File"/home/tdd/superlists/superlists/functional_tests.py",line27,intest_can_start_a_list_and_retrieve_it_laterheader_text=self.browser.find_element(By.TAG_NAME,'h1').text File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 748, in find_element
returnself.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
raiseexception_class(message,screen,stacktrace)selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: h1; 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:8WebDriverError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:193:5NoSuchElementError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:511:5dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16----------------------------------------------------------------------Ran1testin3.870sFAILED (errors=1)
Antes de continuar podemos colocar o código de teste modificado sob controle de versão com os comandos que usamos periodicamente e repetidos abaixo:
(superlists) tdd@mlp:~/superlists/superlists$ git diff
(superlists) tdd@mlp:~/superlists/superlists$ git commit -am "Functional test now checks we can input a to-do item"
(superlists) tdd@mlp:~/superlists/superlists$ git push
Iniciando o Terceiro Passo do Ciclo do TDD (Refatorar)
O processo de refatoração envolve melhorar o código atual sem incluir novas funcionalidades. A adição de funcionalidade durante o processo de refatoração fatalmente levará a problemas indesejados. Além disso, jamais refatore o código sem que você tenha testes que permitam assegurar que possíveis alterações indesejadas sejam barradas.
No nosso exemplo, a refatoração será o uso de templates do Django para exibir o código HTML ao invés de embutir esse HTML diretamente no código da aplicação. Esse isolamento favorece a manutenção futura de nosso produto.
No Django, o recurso utilizado para isso são os templates. Para utilizá-lo, basta criar uma subpasta templates, abaixo de nosso diretório lists (lists/templates). O Django sai varrendo automaticamente essas pastas na busca por aquivos .html que referenciamos no nosso código.
No exemplo acima, criamos o arquivo lists/templates/home.html com o conteúdo que já estava embutido em nosso código views.py e refatoramos views.py conforme abaixo.
from django.shortcuts import render# Create your views here.defhome_page(request):returnrender(request, 'home.html')
A função render acima, aceita um objeto do tipo requisição como primeiro parâmetro e o segundo é um template que será renderizado para exibição. Conforme comentado, o Django varre o diretório templates na busca pelo arquivo home.html indicado no segundo parâmetro.
Para saber se nossa refatoração funcionou basta reexecutar os testes:
(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 25, in test_can_start_a_list_and_retrieve_it_later
self.assertIn('To-Do', self.browser.title)
AssertionError: 'To-Do' not found in 'TemplateDoesNotExist at /'
----------------------------------------------------------------------
Ran 1 test in 4.532s
FAILED (failures=1)
Ops, parece que algo deu errado. Será que nossa refatoração quebrou o nosso código? Na verdade não. O que ocorreu com o erro acima é que ainda não registramos nossa aplicação no Django. Quando rodamos o comando pra criar nossa aplicação (python manage.py startapp lists - Seção 3.5). Após criar uma aplicação no Django, ela precisa ser registrada. Isso é feito incluíndo seu nome no arquivo settings.py, localizado no diretório superlists, um nível abaixo do arquivo manage.py.
Dentro do arquivo settings.py, procure pela seção INSTALLED_APPS e faça a inclusão de nossa aplicação lists ao final da lista, abaixo das aplicações já registradas por padrão pelo Django.
Feita a alteração podemos reexecutar os testes. Observe que ele ainda acusa um erro, mas isso é apenas porque em nosso template teclamos um ENTER após o fechamento da tag '</html>'. Desse modo, nosso assertTrue esta falhando pois a linha não termina apenas com '</html>' mas sim com algo como '</html>\n'.
(superlists) tdd@mlp:~/superlists/superlists$ python manage.py test
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F.
======================================================================
FAIL: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/tdd/superlists/superlists/lists/tests.py", line 19, in test_home_page_returns_correct_html
self.assertTrue(html.endswith('</html>'))
AssertionError: False is not true
----------------------------------------------------------------------
Ran 2 tests in 0.006s
FAILED (failures=1)
Destroying test database for alias 'default'...
Para evitar que isso ocorra, alteramos o nosso arquivo tests.py e usamos a função strip() da classe String do Python que elimina os caracteres em branco antes e após a String. O código dos testes ficaram conforme abaixo:
from django.urls import resolvefrom django.test import TestCasefrom django.http import HttpRequestfrom lists.views import home_pageclassHomePageTest(TestCase):deftest_root_url_resolves_to_home_page_view(self): found =resolve('/') self.assertEquals(found.func, home_page)deftest_home_page_returns_correct_html(self): request =HttpRequest() response =home_page(request) html = response.content.decode('utf-8') self.assertTrue(html.strip().startswith('<html>')) self.assertIn('<title>To-Do lists</title>', html) self.assertTrue(html.strip().endswith('</html>'))
Com essa alteração (linhas 17 e 19 acima) nossos testes passam com sucesso, indicando que nossa refatoração foi bem sucedida.
(superlists) tdd@mlp:~/superlists/superlists$ python manage.py test
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.006s
OK
Destroying test database for alias 'default'...
Django Test Client
Existem diferentes formas de se testar se o Django está renderizando a página correta fazendo uso de nossos templates. Uma delas é fazer a renderização manual, comparando o que a view retorna. Para isso, o Django oferece uma função que auxilia neste processo: render_to_string. Veja a seguir, como fica o nosso caso de teste unitário fazendo uso dessa função:
Observe que as mudanças em relação ao teste anterior é o import na linha 4 e as linhas 18 e 19 que substituíram as antigas linhas de 17 a 19 (asserts). Com a chamada da função render_to_string, o Django nos devolve todo o conteúdo renderizado e armazena na variável expected_html (linha 18) que é comparada, na linha 19, com a resposta decodificada obtida da requisição (linha 17).
Entretanto, quando usamos templates, o Django oferece uma ferramenta ainda mais simples de verificarmos se nossa aplicação respondeu corretamente. Ela se chama Django Test Client. Para mais informações sobre essa ferramenta, o leitor interessado pode consultar a documentação oficial do Django em https://docs.djangoproject.com/en/3.2/topics/testing/tools/#the-test-client. Nosso caso de teste reescrito para fazer uso do Django Test Client é apresentado abaixo:
from django.urls import resolvefrom django.test import TestCasefrom lists.views import home_pageclassHomePageTest(TestCase):deftest_root_url_resolves_to_home_page_view(self): found =resolve('/') self.assertEquals(found.func, home_page)deftest_home_page_returns_correct_html(self): response = self.client.get('/') self.assertTemplateUsed(response, 'home.html')
Observe que agora o nosso teste test_home_page_returns_correct_html ficou muito mais simples. Ao invés de chamarmos manualmente o objeto HttpRequest e de chamar a função de view, basta fazermos uma chamada a self.client.get, passando a URL desejada.
Finalmente, para fazermos o teste se o retorno foi bem sucedido, utilizamos o método assertTemplateUsed da classe TestCase do Django. Ele nos permite confrontar a resposta do cliente com o conteúdo do template de forma mais simples.
Conforme destaca Percival (2017), o ponto principal ao usarmos o Django Test Client é que, "ao invés de testarmos constantes, estamos testando nossa implementação", ou seja, eliminamos constantes do nosso código de teste e o deixamo menos sujeito a manutenções em função das alterações no código da aplicação. Isso é muito importante para minimizarmos os custos de automatização dos testes.
O resultado da execução do teste utilizando o Django Test Client é dado abaixo:
(superlists) tdd@mlp:~/superlists/superlists$ python manage.py test
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.010s
OK
Destroying test database for alias 'default'...
Se ainda não tivermos confiantes de que esse novo modo de testar o retorno da página está funcionando, podemos fazer uma alteração proposital no teste para verificar se o teste ira falhar (linha 13 - template trocado para 'wrong.html'). No exemplo abaixo, ao executar o teste, o resultado indica uma falha pois o template não foi encontrado.
from django.urls import resolvefrom django.test import TestCasefrom lists.views import home_pageclassHomePageTest(TestCase):deftest_root_url_resolves_to_home_page_view(self): found =resolve('/') self.assertEquals(found.func, home_page)deftest_home_page_returns_correct_html(self): response = self.client.get('/') self.assertTemplateUsed(response, 'wrong.html')
Com a alteração, o resultado da execução do teste falha conforme abaixo:
(superlists) tdd@mlp:~/superlists/superlists$ python manage.py test
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F.
======================================================================
FAIL: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/tdd/superlists/superlists/lists/tests.py", line 13, in test_home_page_returns_correct_html
self.assertTemplateUsed(response, 'wrong.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 677, in _assert_template_used
self.assertTrue(
AssertionError: False is not true : Template 'wrong.html' was not a template used to render the response. Actual template(s) used: home.html
----------------------------------------------------------------------
Ran 2 tests in 0.010s
FAILED (failures=1)
Destroying test database for alias 'default'...
Refatorado nosso caso de teste unitário, é hora de colocarmos as modificações sob controle de versão. Utilizamos, em sequência, os comandos: git status, git add ., git commit e git push. O resultado é mostrado abaixo:
(superlists) tdd@mlp:~/superlists/superlists$ git status
No ramo main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(utilize "git add <arquivo>..." para atualizar o que será submetido)
(use "git restore <file>..." to discard changes in working directory)
modified: lists/tests.py
modified: lists/views.py
modified: superlists/settings.py
Arquivos não monitorados:
(utilize "git add <arquivo>..." para incluir o que será submetido)
lists/templates/
nenhuma modificação adicionada à submissão (utilize "git add" e/ou "git commit -a")
(superlists) tdd@mlp:~/superlists/superlists$ git add .
(superlists) tdd@mlp:~/superlists/superlists$ git commit -am "Refactor home page view to use template"
[main d9a2bb2] Refactor home page view to use template
4 files changed, 7 insertions(+), 10 deletions(-)
create mode 100644 lists/templates/home.html
(superlists) tdd@mlp:~/superlists/superlists$ git push
Username for 'https://github.com': aurimrv
Password for 'https://aurimrv@github.com':
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 3 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (9/9), 817 bytes | 817.00 KiB/s, done.
Total 9 (delta 6), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (6/6), completed with 6 local objects.
To https://github.com/aurimrv/superlists.git
9168eb7..d9a2bb2 main -> main
Finalmente, para encerrar esta seção, terminamos com a dica de Percival (2017) sobre refatoração e TDD.
"Ao refatorar, trabalhe no código ou nos testes, mas não em ambos ao mesmo tempo." (Percival, 2017)
Corrigindo a Aplicação para Evoluir no Atendimento do Teste Funcional
Agora que refatoramos nosso teste unitário, podemos iniciar a correção da nossa aplicação para evoluir no atendimento do teste funcional. Para termos uma ideia, ao executar os testes funcionais, obteremos o seguinte resultado:
(superlists) tdd@mlp:~/superlists/superlists$pythonfunctional_tests.pyE======================================================================ERROR:test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)----------------------------------------------------------------------Traceback (most recentcalllast):File"/home/tdd/superlists/superlists/functional_tests.py",line27,intest_can_start_a_list_and_retrieve_it_laterheader_text=self.browser.find_element(By.TAG_NAME,'h1').text File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 748, in find_element
returnself.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
raiseexception_class(message,screen,stacktrace)selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: h1; 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:8WebDriverError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:193:5NoSuchElementError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:511:5dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16----------------------------------------------------------------------Ran1testin3.884sFAILED (errors=1)
O erro reportado na linha 15 é de que não foi possível encontrar o elemento h1. Realmente, não usamos h1 em nosso template. Assim sendo, podemos melhorar nossa aplicação editando o arquivo lists/templates/home.html conforme abaixo:
Com a alteração, o resultado dos testes muda. Agora o Selenium não está encontrando o elemento com id="id_new_item" (linha 15 da mensagem abaixo).
(superlists) tdd@mlp:~/superlists/superlists$pythonfunctional_tests.pyE======================================================================ERROR:test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)----------------------------------------------------------------------Traceback (most recentcalllast):File"/home/tdd/superlists/superlists/functional_tests.py",line33,intest_can_start_a_list_and_retrieve_it_laterinputbox=self.browser.find_element(By.ID,'id_new_item') File "/home/tdd/.pyenv/versions/superlists/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 748, in find_element
returnself.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
raiseexception_class(message,screen,stacktrace)selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [id="id_new_item"]; 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:8WebDriverError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:193:5NoSuchElementError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:511:5dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16----------------------------------------------------------------------Ran1testin3.652sFAILED (errors=1)
Vamos resolver isso alterando novamente nosso templatelists/templates/home.html. Podemos incluir o código HTML conforme abaixo:
Evoluímos, agora nossos testes param na mensagem abaixo:
(superlists) tdd@mlp:~/superlists/superlists$pythonfunctional_tests.pyF======================================================================FAIL:test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)----------------------------------------------------------------------Traceback (most recentcalllast):File"/home/tdd/superlists/superlists/functional_tests.py",line34,intest_can_start_a_list_and_retrieve_it_laterself.assertEqual(AssertionError:''!='Enter a to-do item'+Enterato-doitem----------------------------------------------------------------------Ran1testin4.099sFAILED (failures=1)
Vamos melhorar nosso template para atender a essa demanda dos testes funcionais. Para isso, o código do nosso templatelists/templates/home.htmlficará conforme abaixo. Utilizamos o atributo placeholder do HTML para dica para um item ser fornecido.
Avançamos mais um pouco e o resultado dos nossos testes agora indicam um erro por não encontrar o elemento com id="id_list_table" (linha 15 da mensagem abaixo).
(superlists) tdd@mlp:~/superlists/superlists$pythonfunctional_tests.pyE======================================================================ERROR:test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)----------------------------------------------------------------------Traceback (most recentcalllast):File"/home/tdd/superlists/superlists/functional_tests.py",line53,intest_can_start_a_list_and_retrieve_it_latertable=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
returnself.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
raiseexception_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:8WebDriverError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:193:5NoSuchElementError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:511:5dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16----------------------------------------------------------------------Ran1testin4.847sFAILED (errors=1)
Vamos a ele então. A correção do nosso template para atender a essa demanda do teste funcional pode ser conforme abaixo:
Com a inclusão da tag table, a execução dos testes para em um novo erro relacionado com a linha 55 do nosso arquivo functional_tests.py. Parece um erro meio obscuro e está relacionado com aquele assertTrue contendo o comando any dentro dele.
Da forma como o assertTrue está a mensagem de erro gerada não é muito significativa. A maioria dos métodos assert do unittest aceitam um último parâmetro que é uma string exibida quando o assert falha. Podemos utilizar esse recurso para oferecer uma mensagem de erro mais significativa nesse caso. O comando assertTrue poderia ser reescrito para:
self.assertTrue(any(row.text =='1: Buy peacock feathers'for row in rows),"New to-do item not appear in table" )
Com isso, a mensagem exibida ficou mais clara, correto?! Entretanto, fazer esse teste passar irá demandar um esforço maior pois precisaremos processar os dados enviados no formulário. Faremos isso no próximo capítulo pois precisaremos, além de processar, armazenar os dados em um banco de dados para posteriormente recuperarmos os itens da lista.
(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
----------------------------------------------------------------------
Ran 1 test in 5.498s
FAILED (failures=1)