Testes E2E para Aplicações Web Spring: Renderizando uma Lista
Depois de concluirmos esta aula, nós:
- Saberemos como criar Page Objects para uma página que renderiza uma lista de itens (
TaskListPageeTaskListItem). - Entenderemos como embutir metadados semânticos no HTML (como IDs e status em atributos
ideclass) quando uma informação exigida pelo teste não está visível em texto puro. - Compreenderemos os desafios que enfrentamos quando inicializamos o banco de dados apenas uma vez no startup da aplicação versus antes de cada teste, e saberemos escrever testes End-to-End resilientes para páginas de listagem.
1. Introdução ao Sistema Sob Teste (SUT): A Página de Listagem de Tarefas
Durante esta aula, escreveremos testes End-to-End para a funcionalidade que renderiza a página de listagem de tarefas (task list page). Essa página é renderizada quando um usuário autenticado envia uma requisição GET para o caminho raiz /, e atende aos seguintes requisitos de negócio:
- Deve exibir a quantidade de tarefas encontradas no banco de dados.
- Deve exibir a tabela de tarefas (
#task-list) encontradas no banco de dados. - Deve exibir o título (
.task-list-item-title) e o status traduzido/localizado (.task-list-item-status) de cada tarefa. - O título de cada tarefa deve ser um link clicável que abre a página de visualização daquela tarefa (
/task/{taskId}).
Entretanto, essa página possui um desafio comum em interfaces reais: ela não exibe diretamente em texto puro todas as informações técnicas que nossos testes End-to-End precisam verificar (como o id numérico da tarefa no banco ou a constante do enum TaskStatus independente do idioma da tradução).
Por isso, adicionamos duas informações estruturadas aos atributos HTML de cada linha da tabela:
- O
idda tarefa exibida é concatenado ao atributoiddo elemento<tr>que envolve os dados daquela tarefa:<tr id="task-list-item-${task.id}" class="task-list-item">. - O nome do enum
statusda tarefa (OPEN,IN_PROGRESS,CLOSED) é concatenado ao atributoclassdo elemento<td>que exibe o status traduzido da tarefa:<td class="task-list-item-status task-status-${task.status}">.
O código-fonte do template HTML/JSP (list.jsp) fica assim:
|
|
Vamos avançar e criar os dois Page Objects necessários para interagir com essa tabela.
2. Criando os Page Objects Necessários (TaskListPage e TaskListItem)
Antes de escrevermos nossos testes End-to-End para a listagem de tarefas, precisamos criar dois Page Objects que trabalham em conjunto:
1. O Page Object TaskListPage
A classe TaskListPage representa a página de listagem de tarefas como um todo. Ela possui quatro métodos:
getListItems(): Localiza todos os elementos com a classe.task-list-itemno documento HTML, transforma cadaWebElementencontrado em um objetoTaskListItem(browser, element)e retorna a listaList<TaskListItem>.getPageUrl(): Retorna a URL da página de listagem de tarefas.isOpen(): Retornatruese a página de listagem de tarefas estiver aberta no navegador efalsecaso contrário.open(): Abre a página de listagem de tarefas e retorna uma nova instância deTaskListPage.
O código-fonte da classe TaskListPage em Kotlin fica assim:
|
|
2. O Page Object de Item de Lista TaskListItem
A classe TaskListItem é um Page Object que representa uma única linha (<tr>) da tabela de tarefas exibida na TaskListPage. Criar um Page Object dedicado para um item de lista é uma excelente prática de design porque mantém as responsabilidades bem separadas e evita que TaskListPage fique poluída com métodos de extração de colunas.
A classe TaskListItem possui quatro métodos:
getId(): Extrai oidnumérico da tarefa a partir do atributoiddo elemento<tr>("task-list-item-1"->1L).getTitle(): Retorna o texto do link.task-list-item-titledentro daquela linha.getStatus(): Extrai a constante do enumTaskStatusa partir do atributoclassdo elemento<td>(.task-list-item-status), removendo os prefixos de classe CSS ("task-list-item-status"e"task-status-") e convertendo a string resultante comTaskStatus.valueOf(status). Isso torna o nosso teste 100% imune a mudanças de tradução/i18n na interface!viewTask(): Clica no link.task-list-item-titlepara abrir a página de detalhes da tarefa, aguarda o redirecionamento comWebDriverWaite retorna um novo objetoTaskPage.
O código-fonte da classe TaskListItem fica assim:
|
|
A seguir, vamos descobrir como utilizar TaskListPage e TaskListItem para escrever nossos testes End-to-End da funcionalidade de listagem de tarefas.
3. Escrevendo Testes End-to-End para a Listagem de Tarefas
Podemos escrever nossos testes End-to-End seguindo estes 5 passos:
Passos 1 a 4: Configurar a Classe de Teste, @BeforeEach (Login) e @AfterEach (Logout)
Assim como fizemos na aula anterior:
- Criamos a classe
RenderTaskListPageTestanotada com@SeleniumTest. - Adicionamos o campo
private lateinit var taskListPage: TaskListPage. - No método
@BeforeEach, autenticamos o usuárioJohnDoeusandoLoginPage(browser).open().login(...)e instanciamosTaskListPage(browser). - No método
@AfterEach, encerramos a sessão do usuário chamandoNavigationBar(browser).logUserOut().
Passo 5: Lidando com o Estado do Banco e Escrevendo os 4 Testes
Se estivermos inicializando nosso banco de dados apenas uma vez quando a aplicação web é iniciada (por exemplo, via Liquibase em uma aplicação Spring clássica, como vimos na Aula 04 do Tópico 06), escrever testes para uma página de listagem exige cuidado extra porque não sabemos a ordem exata em que os testes End-to-End da suíte serão executados:
- Nosso script SQL inicial insere 3 tarefas (
id = 1,id = 2eid = 3). - Porém, nossa suíte E2E também possui um teste que deleta a terceira tarefa (
id = 3) e outro teste que cria uma nova tarefa (id = 4), além de um teste que atualiza a segunda tarefa (id = 2). - Portanto, se o banco não for resetado a cada teste, sabemos que no momento em que
RenderTaskListPageTestrodar:- Existirão pelo menos 2 tarefas no banco de dados (
tasks.size >= 2). - A primeira tarefa (
id = 1—WriteExampleApp) nunca é modificada nem excluída por nenhum outro teste, então podemos fazer asserções exatas sobre todos os campos do primeiro item da lista (tasks[0])!
- Existirão pelo menos 2 tarefas no banco de dados (
Nota sobre Spring Boot +
@Sql: Se você estiver escrevendo testes End-to-End para uma aplicação Spring Boot e utilizando@Sqlpara resetar e popular o banco antes de cada método de teste (como vimos na Aula 05 do Tópico 06), você não terá essa limitação, pois o banco sempre terá exatamente as 3 tarefas originais no início de cada método@Test!
Vamos analisar os quatro métodos de teste da nossa classe RenderTaskListPageTest:
- Verificar se conseguimos abrir a página de listagem de tarefas (
shown.isOpen()). - Garantir que a página de listagem exibe pelo menos duas tarefas (
assertThat(tasks.size).isGreaterThanOrEqualTo(2)). - Verificar se a página de listagem exibe o
id,titleestatuscorretos da primeira tarefa (WriteExampleApp). - Garantir que conseguimos navegar da página de listagem para a página de visualização da tarefa clicando no primeiro item (
first.viewTask()).
O código completo da classe RenderTaskListPageTest fica assim:
|
|
E a constante EndToEndTestTasks.WriteExampleApp utilizada pelas asserções acima possui a seguinte definição:
|
|
Resumo
Esta aula nos ensinou três lições fundamentais:
- Se uma informação exigida pelos nossos testes End-to-End (como o
idda entidade ou a chave não traduzida de umenum) não estiver diretamente visível em texto na interface do usuário, podemos embutir essa informação de forma limpa nos atributosid,classoudata-*do elemento HTML renderizado. - É uma excelente prática criar um Page Object dedicado para representar um único item de uma lista (
TaskListItem). Isso mantém a classeTaskListPageenxuta e separa claramente as responsabilidades. - Se inicializarmos nosso banco de dados apenas uma vez quando a aplicação web é iniciada (sem resetar via
@Sqla cada método de teste), devemos escrever asserções que não dependam da ordem de execução de outros testes que inserem, editam ou removem outras linhas da tabela.