Programação e Desenvolvimento Dirigidor por Testes
  • Programação e Desenvolvimento Dirigidor por Testes em Python
  • Autores e Agradecimentos
  • Uso do Livro
  • Contribua com o Livro
  • Licença
  • Organização do Livro
  • 1 Introdução
    • 1.1 Considerações Iniciais
    • 1.2 Configuração Inicial do Ambiente
    • 1.3 TDD Básico
      • 1.3.1 Exemplo Simples
    • 1.4 Considerações Finais
  • 2 TESTE DE SOFTWARE
    • 2.1 Considerações Iniciais
    • 2.2 Terminologia e Conceitos Básicos
    • 2.3 Fases de Teste
    • 2.4 Técnicas e Critérios de Teste
    • 2.5 Considerações Finais
  • 3 Desenvolvimento Dirigido por Teste
    • 3.1 Configuração do Ambiente
    • 3.2 Verificando o Ambiente com TDD
    • 3.3 Controle de Versão do Projeto
    • 3.4 Teste Funcional com UnitTest
    • 3.5 Teste de Unidade e a Evolução do Sistema
      • 3.5.1 Teste de Unidade de uma View
    • 3.6 Evoluindo o Teste Funcional
    • 3.7 Revisando o Processo do TDD
  • 4 TDD E BANCO DE DADOS
    • 4.1 Envio e Processamento de Requisição POST
    • 4.2 Banco de Dados no Django
  • 5 MELHORANDO E ORGANIZANDO OS CONJUNTOS DE TESTE
    • 5.1 Isolamento dos Testes Funcionais
    • 5.2 Esperas Implícitas, Explícitas e Time Sleeps
  • 6 ATACANDO O MAIOR DESAFIO DE FORMA INCREMANTAL
    • 6.1 Separando URL no Estilo REST
    • 6.2 Iterando para um Novo Design da Aplicação
    • 6.3 Refatorar Casos de Teste
    • 6.4 Separando Templates
    • 6.5 URL para Nova Lista
    • 6.6 Alterando Modelos
    • 6.7 URL Próprio para Cada Lista
Powered by GitBook
On this page

Was this helpful?

  1. 3 Desenvolvimento Dirigido por Teste

3.4 Teste Funcional com UnitTest

Previous3.3 Controle de Versão do ProjetoNext3.5 Teste de Unidade e a Evolução do Sistema

Last updated 10 months ago

Was this helpful?

O é uma ferramenta capaz de simular as ações dos usuários em aplicações Web. Desse modo, ele pode e será utilizado por nós na escrita dos chamados Testes Funcionais, ou seja, testes que executam as funcionalidades do sistema.

Considerando a Pirâmide de Teste apresentada na , classificamos os testes funcionais como sendo testes do topo da piramide, ou seja, testes funcionais são considerados aqui testes de sistema ou testes fim-a-fim.

prega que os testes funcionais devem, na verdade, descrever histórias do usuário, sendo, os testes, um artefato a mais para se compreender as funcionalidades que o sistema deve possuir. Ele defende que as histórias sejam incluídas como comentários no teste funcional. Por exemplo, considerando o TDD e as metodologias ágeis, é comum se falar em produto mínimo viável (do Inglês - MVP - Minimal Viable Product). O MVP corresponde ao artefato mais simples que possamos construir e que seja viável.

A aplicação que será utilizada para isso será uma lista de tarefas. Nesse sentido, uma lista de tarefas mínima, conforme descrito por , deve permitir ao usuário inserir itens referentes a tarefas e posteriormente, ser lembrado das tarefas a serem executadas.

Considerando o teste funcional feito anteriormente (functional_tests.py), poderíamos editá-lo para representar uma história inicial, conforme abaixo.

from selenium import webdriver

browser = webdriver.Firefox()

# Edith ouviu falar de uma nova aplicação online interessante
# para lista de tarefas. Ela decide verificar a homepage

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

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

assert 'To-Do' in browser.title

# Ela é convidada a inserir um item de tarefa imediatamente

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

# 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

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

# 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 dormir

browser.quit()

Observa-se que os comentários são utilizados para descrever a história e, algumas partes deles já são implementadas como teste.

Executando o Teste

Para executar esse teste, primeiramente precisamos inicializar o django para, em seguida, executar o teste. Assumindo que não temos terminais abertos, executaremos, inicialmente os comandos abaixo para inicializar o django.

tdd@mlp:~$ pyenv activate superlists 
(superlists) tdd@mlp:~$ cd superlists/superlists/
(superlists) tdd@mlp:~/superlists/superlists$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
June 22, 2024 - 14:28:58
Django version 5.0.6, using settings 'superlists.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Em seguida, para executar os testes, devemos abrir um novo terminar e executar os comandos abaixo:

tdd@mlp:~/superlists/superlists$ cd
tdd@mlp:~$ pyenv activate superlists 
(superlists) tdd@mlp:~$ cd superlists/superlists/
(superlists) tdd@mlp:~/superlists/superlists$ python functional_tests.py
Traceback (most recent call last):
  File "/home/tdd/superlists/superlists/functional_tests.py", line 13, in <module>
    assert 'To-Do' in browser.title
AssertionError
(superlists) tdd@mlp:~/superlists/superlists$ 

Como será observado, o teste faz com que a janela do Firefox abra com o conteúdo abaixo e, desse modo, como o título não contém a palavra To-Do, o teste apresenta uma mensagem de erro no prompt, conforme mostrado nas linhas de 5 a 8 da saída exibida na mensagem acima.

Antes de continuar, como fizemos alterações importantes no nosso arquivo functional_tests.py, tais alterações merecem ser confirmadas no nosso Git. Para isso, podemos executar os comandos 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:   functional_tests.py

nenhuma modificação adicionada à submissão (utilize "git add" e/ou "git commit -a")
(superlists) tdd@mlp:~/superlists/superlists$ git add functional_tests.py
(superlists) tdd@mlp:~/superlists/superlists$ git commit -m "Improve history of functional test"
[main a7e1e5e] Improve history of functional test
 1 file changed, 36 insertions(+), 1 deletion(-)

Para enviar as alterações para o Github basta usar os comandos abaixo, considerando seu login e token de usuário do GitHub.

(superlists) tdd@mlp:~/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 3 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 931 bytes | 931.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/aurimrv/superlists.git
   010bb52..a7e1e5e  main -> main

Módulo UnitTest

O trecho de código abaixo, ilustra as alterações feitas no nosso caso de teste para torná-lo aderente ao framework unittest. Os detalhes das alterações são explicados a seguir.

from selenium import webdriver
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)
		self.fail('Finish the test!')

		# Ela é convidada a inserir um item de tarefa imediatamente

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

		# 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

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

		# 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 dormir

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

O primeiro ponto a ser observado é que no unittest, os testes são organizados dentro de uma classe que herda de unittest.TestCase (linha 4).

Um teste propriamente dito, é um método dentro dessa classe cujo nome se inicia por test_, como ocorre em test_can_start_a_list_and_retrieve_it_later (linha 12). Podemos ter vários métodos iniciando com test_dentro da mesma classe e é sempre bom dar nomes significativos para os testes para auxiliar na identificação de seus objetivos.

Existem dois métodos especiais presentes na nossa classe de test: setUp e tearDown. Eles servem para executar ações sempre antes e após a execução de cada teste, respectivamente. No caso, dentro do setUp nós estamos inicializando o navegador (linhas 6 e 7) e no tearDown encerramos sua execução (linhas 9 e 10). Recordando a terminologia de casos de teste apresentada durante o curso, ao utilizar tais métodos, a intenção é tornar cada caso de teste independente pois iremos reiniciar o estado do sistema sempre, antes da execução de cada novo teste.

O método self.fail faz o teste falhar (linha 22), independentemente de qualquer outras verificação de sucesso realizada. Está sendo usado apenas como um lembrete de que o teste ainda não está finalizado e, portanto, deve falhar ao atingir esse ponto.

Finalmente, o código if __name__ == '__main__' (linha 50) é a forma que o Python utiliza para identificar que um determinado programa é invocado via linha de comando para, então, executar o método principal do unittest.main.

Ao executar novamente o caso de teste veremos a diferença de comportamento, principalmente com a janela do navegador abrindo e fechando rapidamente, e o resultado da execução do teste aparecendo no prompt, conforme apresentado a seguir.

(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 21, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('To-Do', self.browser.title)
AssertionError: 'To-Do' not found in 'The install worked successfully! Congratulations!'

----------------------------------------------------------------------
Ran 1 test in 3.649s

FAILED (failures=1)

No exemplo acima podemos observar que o teste falhou pois a string "To-Do" não foi encontrada no título do navegador, o qual apresenta a string padrão da execução do django; "The install worked successfully! Congratulations!"

Novamente, devido as mudanças no arquivo de casos de testes é interessante fazermos um novo commit para confirmar as alterações. No exemplo abaixo, utilizamos um git commit -a que adiciona e confirma automaticamente todas as mudanças realizadas no repositório.

(superlists) tdd@mlp:~/superlists/superlists$ git commit -a -m "First unittest test case"
[main f641e81] First unittest test case
 1 file changed, 51 insertions(+), 41 deletions(-)
 rewrite functional_tests.py (96%)
(superlists) tdd@mlp:~/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 3 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 649 bytes | 649.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/aurimrv/superlists.git
   a7e1e5e..f641e81  main -> main

Vamos executar esse teste? Já sabemos antecipadamente o resultado do mesmo, correto? Ele irá falar. Isso é o que classifica como uma falha esperada, ou seja, como o sistema ainda não implementa a funcionalidade desejara, o teste deveria falar.

Alguns aspectos com o nosso caso de teste chamam atenção e merecem ser melhorados. Um deles é o fato da janela do navegador ficar aberta após o término do caso de teste. O ideal seria que a mesma fosse fechada. Para nos auxiliar nessa e em outras tarefas, vamos utilizar um dos diversos frameworks para a execução automática de testes em Python. Nesse caso, faremos uso do módulo .

Para verificar se o texto desejado aparece no título do navegador estamos utilizando o método self.assertIn, disponível no framework (linha 21). Além dele, o framework também disponibiliza outros métodos de verificação como assertEquals, assertTrue, dentre outros. Uma lista completa dos métodos de verificação pode ser encontrada na página do framework, disponível em .

Selenium
Seção 2.3
Percival (2017)
Percival (2017)
Percival (2017)
unittest
https://docs.python.org/3/library/unittest.html#assert-methods
Tela do django com a execução do caso de teste