Hacks de QA: Como utilizar ID’s de teste no desenvolvimento de componentes de interface gráfica?

O que falar desse frontend que mal conheço e já considero pakas?

Júnior Sbrissa
assert(QA)
14 min readMar 27, 2023

--

Pessoas analisando um frontend de uma aplicação por Freepik

Checklist do conteúdo

  • Qual o nosso ponto de partida?
  • Qual a definição de componente nesse contexto?
  • Qual o objetivo do teste de componente de interface?
  • Qual a proposta para utilizar ID’s de teste em componentes?
  • Qual a proposta para compor ID’s de teste em componentes?
  • Qual o processo para remover ID’s em duplicidade?
  • Quais as orientações de desenvolvimento?
  • Quais as referências e materiais de apoio desse artigo?
  • TL;DR

Precisamos conversar sobre alguns conceitos

Abram seus livros por Giphy

Antes de prosseguirmos pela leitura principal, para que possamos ter um entendimento mais completo do assunto deste artigo, gostaria de iniciar nossa conversa destacando algumas definições que acredito serem importantes para a compreensão do conteúdo abordado.

Vamos lá!

O backend é a parte “invisível” da aplicação, responsável por realizar tarefas complexas, como acesso a bancos de dados, processamento de dados e outras funcionalidades de negócio.

Já o frontend é a parte da aplicação que o usuário interage diretamente, como a interface gráfica de usuário (GUI). É responsável por apresentar informações ao usuário e gerenciar suas interações com a aplicação.

A caixa de pandora do frontend

Para tornar o frontend mais escalável e fácil de manter, os desenvolvedores usam a técnica de dividir em componentes. Cada componente é uma parte menor e independente da interface, responsável por apresentar uma funcionalidade específica. Esses componentes podem ser reutilizados em diferentes partes da aplicação.

Além disso, para fornecer funcionalidades adicionais aos componentes, é comum no mercado corporativo a utilização de services. Eles são recursos específicos que podem ser usados por vários componentes, fornecendo funcionalidades como acesso a dados ou cálculos complexos.

Transpilando a parte técnica

Num primeiro momento entender os conceitos desse funcionamento pode parecer complexo, eu sei. Então vamos olhar para essa arquitetura de uma outra maneira.

Imagine que você está construindo uma casa.

Nesse nosso exemplo, o frontend seria a fachada e o interior da casa, ou seja, o que você pode ver e interagir. Nosso backend seria composto pelos sistemas elétrico e hidráulico (água e esgoto), características que não são visíveis mas são cruciais para o funcionamento da casa.

Os componentes seriam os cômodos da casa: cozinha, quarto, banheiro, etc. Eles são partes menores e independentes que juntas formam a casa completa.

Os services seriam como as instalações dentro de cada cômodo: os interruptores para ascender as luzes, o fogão na cozinha, a mesa do escritório, o chuveiro no banheiro, etc. Eles são recursos específicos que tornam a casa mais funcional.

A importância em compreender tudo isso

A arquitetura de software moderna é crucial para garantir a manutenibilidade adequada de uma aplicação. De acordo com a ISO-25010, essa característica é uma medida da facilidade e eficiência com que uma aplicação pode ser mantida, corrigida ou melhorada.

A separação em frontend e backend, a organização em componentes e o uso de recursos específicos são conceitos da arquitetura de software moderna que permitem construir aplicações escaláveis e de fácil manutenção.

Ao entender e aplicar esses conceitos corretamente, os responsáveis pelo desenvolvimento são capazes de criar aplicações que atendam às boas práticas previstas nos padrões da arquitetura de software moderna ao mesmo tempo que colaboram com o conceito de manutenibilidade.

Definindo componente para o nosso contexto

O conceito de estratégia por Giphy

Na definição contextual aqui abordada, um componente é definido como todo artefato visual elaborado para ser utilizado como recurso dentro de uma interface gráfica.

Classificação dos componentes

Componentes podem ser divididos em 2 grupos: primitivos e compostos.

Sua classificação, além de ser orientada pelo modo que o componente é desenvolvido, também observa algumas características que ajudam o enquadramento em um dos grupos descritos.

Componentes primitivos

  • Possuem estrutura simples.
  • São altamente reutilizáveis.
  • Não possui lógica de negócio.
  • Exemplo: labels, buttons, inputs, image.

Componentes compostos

  • Definido em alinhamento entre Dev Team e UX/UI.
  • Por vezes possuem estrutura complexa.
  • São construídos com componentes primitivos.
  • São desenvolvidos para atender uma funcionalidade.
  • Carregam lógica negocial para colaborar com a funcionalidade.
  • Exemplo: formulários, seções, páginas.

Entendendo o teste de componente de interface

Entendi, eu acho por Giphy

O termo teste de componente possibilita algumas interpretações em sua utilização. Para entender o objetivo em seu emprego, é importante compreender também o contexto que acompanha seu uso.

Na definição prevista pela CTFL, o teste de componente é apresentado como o que comumente conhecemos como teste de unidade. Enquanto isso, no guia de uso do Cypress um componente é tratado como um elemento desenvolvido para compor uma tela, podendo ser composto por outros componentes menores (como por exemplo um formulário com 2 campos e 1 botão).

Em ambas as situações uma característica em comum pode ser notada:

O teste a nível de componente é realizado sem nenhuma integração real com demais partes e elementos externos ao componente.

Objetivo do teste de componente de interface

Ao exercitar testes em um componente de interface é necessário ter em mente alguns pontos de atenção que colaboram com o desenvolvimento dos testes.

Unidades do componente

O teste de componente é responsável por avaliar as unidades do componente.

As unidades podem ser definidas observando algumas características.

  • Validação dos elementos que formam o componente.
  • Validação dos eventos e dados emitidos pelo componente.
  • Validação dos eventos e dados recebidos pelo componente.
  • Regras de estrutura dos elementos do componente.
  • Regras de estados dos elementos.
  • Regras de campos obrigatórios.
  • Regras de mascaramento dos dados.
  • Regras de tipos de uso de dados (documento, data, valores, etc).

Interações internas do componente

O teste de componente é responsável por avaliar interações entre componentes internos de um componente composto.

Além dos itens que apoiam a definição de unidades do componente, temos outras características que podem auxiliar na identificação de interações internas do componente.

  • Regras de filtro de dados.
  • Regras lógicas dos elementos do componente.
  • Regras condicionais dos elementos do componente.

Services do componente

A ausência de visual gráfico para um código escrito no contexto frontend não justifica a falta de necessidade de exercitar esse desenvolvimento por meio de testes. Assim são os services de um componente.

Services devem ser testados em um escopo distinto da parte gráfica.

Ao pensar em boas práticas, os testes desse contexto de validação devem ficar apartados dos testes que observam a parte gráfica do componente. Separar o teste do service do teste que exercita a parte gráfica trás alguns benefícios ao desenvolvimento.

  • Colabora com o design de arquitetura do componente.
  • Colabora com o princípio da responsabilidade única.
  • Colabora com a visão de camadas do software.
  • Diminui a complexidade do teste.
  • Simplifica a lógica do teste.

Utilizando ID’s de teste em componentes

Tudo sob controle por Giphy

Para seguir a proposta de utilização de ID’s para testes de componente aqui abordada é necessário observar as seguintes premissas.

Utilize o atributo data-testid como identificador na estrutura do componente.

  • O uso do identificador colabora com os critérios de testabilidade.

Componentes compostos devem possuir um identificador de escopo geral.

  • Esse ID funciona como delimitador da estrutura do componente desenvolvido.
  • Sinalizar os limites de um componente composto ajuda na resolução de duplicidades.

Componentes primitivos devem possuir um identificador de escopo específico.

  • Esse ID funciona como um facilitador para a interação com o elemento.
  • Normalmente fazem parte da estrutura interna de um componente composto.

Tenha cuidado para não sobrescrever identificadores de testes definidos previamente ao utilizar/reutilizar componentes durante o processo de desenvolvimento.

Exemplos de uso das regras

É importante ressaltar que os exemplos são apenas para fins demonstrativos das regras apresentadas anteriormente. O HTML detalhado a seguir no que diz respeito à composição de estruturas, nomenclaturas e propriedades deve ser considerado como ilustrativo.

Exemplo 1

<form action="#" onsubmit="return submitForm()" data-testid="container-my-component">

Foo field label
<input type="text" name="first-name" data-testid="input-foo" />

<input type="submit" value="Submit" data-testid="input-submit" />

</form>

Exemplo 2

<div class="this-is-my-class" data-testid="container-my-component">

<label class="label"> Bar field label </label>
<div class="box">
<input type="text" class="input" data-testid="input-bar" />
</div>

<button role="button" class="button" data-testid="button-submit">
<span class="children"> Submit </span>
</button>

</div>

Compondo ID’s de teste para componentes

Como fazer essa coisa por Giphy

Para seguir a proposta de composição de ID’s para testes de componente aqui abordada é necessário observar as seguintes premissas.

O padrão definido para essa elaboração de ID’s considera os itens abaixo como fundamentais para o processo composição dos nomes.

  • Alinhamento conjunto entre o time de desenvolvimento.
  • Transcrever o nome dos ID’s preferencialmente em inglês.

Componentes compostos

  • Devem ser identificados pelo prefixo container.
  • Devem possuir em sua identificação o nome definido para o componente.
  • Exemplo: data-testid=”container-payment-setup”.

Componentes primitivos

  • Devem ser identificados pela tag HTML ou pela role do componente.
  • Devem possuir em sua identificação o nome definido para o elemento.
  • Exemplo: data-testid=”input-account-number”.

Exemplos de uso das regras

É importante ressaltar que os exemplos são apenas para fins demonstrativos das regras apresentadas anteriormente. O HTML detalhado a seguir no que diz respeito à composição de estruturas, nomenclaturas e propriedades deve ser considerado como ilustrativo.

Exemplo 1

<div class="this-is-my-area" data-testid="container-my-feature">

<h3 class="title">Test your form</h3>

<div class="this-is-my-filter" data-testid="container-filter-data">
<div class="row">
<label class="label"> Filter field label </label>
<div class="select-wrapper">
<select class="select" data-testid="select-filter-data">
<option value="lorem"> Lorem </option>
<option value="ipsum"> Ipsum </option>
</select>
</div>
</div>
</div>

<div class="this-is-my-form" data-testid="container-some-form">
<div class="row">
<label class="label"> Foo field label </label>
<div class="box">
<input type="text" class="input" data-testid="input-foo" />
</div>
</div>

<div class="row">
<label class="label"> Bar field label </label>
<div class="box">
<input type="number" class="input" data-testid="input-bar" />
</div>
</div>

<div class="row">
<button role="button" class="button" data-testid="button-send">
<span class="children"> Send </span>
</button>
</div>
</div>

</div>

Removendo duplicidade entre os ID’s de teste

Duplicados chirrion por Giphy

Caso seja identificado algum tipo de duplicidade nos ID’s de teste a nível da interface (e que não esteja apta à resolução por contexto apresentada anteriormente), essa repetição pode ser eliminada observando as seguintes orientações.

É importante ressaltar que diferente de uma duplicidade de ID’s em HTML, a duplicidade de ID’s de teste não apresenta um escopo problemático quando as orientações que definem contexto estão sendo bem praticadas.

Orientação 1

O componente com duplicidade herdará em seu ID de teste a nomenclatura utilizada no ID de teste do nível superior ao seu.

  • Essa premissa se aplica para componentes primitivos e compostos.
  • Verificar o padrão para utilização do ID de teste do componente.
  • Heranças serão utilizadas como prefixo do ID de teste duplicado.
  • O prefixo container não deve ser utilizado ao aplicar herança da nomenclatura.

Orientação 2

A herança de nomenclatura segue ao próximo nível caso a duplicidade continue.

  • Uma reanálise de nomenclatura utilizada para compor o ID de teste é recomendada caso dois níveis de herança não sejam o suficiente para resolver a duplicidade.

Orientação 3

Duplicidades em elementos como listas e tabelas podem ser resolvidas adicionando o atributo data-testid para cada linha da estrutura juntamente com o index da iteração/repetição.

  • Essa premissa se aplica apenas à componentes com algum tipo de exibição de dados em lista.
  • O index deve ser adicionado ao ID de teste da linha da estrutura sem qualquer mudança no número da iteração do laço.
  • O index deve ser adicionado ao final do ID de teste da linha da estrutura precedido de hífen.

Exemplos de uso das regras

É importante ressaltar que os exemplos são apenas para fins demonstrativos das regras apresentadas anteriormente. O HTML detalhado a seguir no que diz respeito à composição de estruturas, nomenclaturas e propriedades deve ser considerado como ilustrativo.

Você poderá ao notar no decorrer das amostras de código que a duplicidade do ID de teste para os exemplos 1 e 2 não precisaria ser removida pois o contexto do elemento resolveria o problema ao construir seus seletores.

/* exemplo 1 usando CSS selector */
div[data-testid="container-first-card"] input[data-testid="input-card-number"]

/* exemplo 2 usando CSS Selector */
div[data-testid="container-right"] input[data-testid="input-card-number"]
// exemplo 1
// usando Vue Test Utils
wrapper.find('div[data-testid="container-first-card"] input[data-testid="input-card-number"]')

// usando Testing Library - Native
const container = document.getByTestId('container-first-card')
const inputCard = within(container).getByTestId('input-card-number')

// usando Testing Library - Cypress
cy.findAllByTestId('container-first-card').within(() => {
cy.findAllByTestId('input-card-number').should('exist')
})

// exemplo 2
// usando Vue Test Utils
wrapper.find('div[data-testid="container-right"] input[data-testid="input-card-number"]')

// usando Testing Library - Native
const container = document.getByTestId('container-right')
const inputCard = within(container).getByTestId('input-card-number')

// usando Testing Library - Cypress
cy.findAllByTestId('container-right').within(() => {
cy.findAllByTestId('input-card-number').should('exist')
})

Exemplo 1

Removendo duplicidade em primeiro nível de herança (contexto pai).

Síntese

...
container-payment-setup
├── container-first-card
| └── first-card-input-card-number
├── container-second-card
| └── second-card-input-card-number
...

HTML

<div class="this-is-my-area" data-testid="container-payment-setup">

<h3 class="title">Split payment</h3>

<div class="this-is-my-class" data-testid="container-first-card">
<div class="row">
<label class="label"> Card number </label>
<div class="box">
<!-- input com ID duplicado -->
<input type="number" class="input"
data-testid="first-card-input-card-number" />
</div>
</div>
</div>

<div class="this-is-my-class" data-testid="container-second-card">
<div class="row">
<label class="label"> Card number </label>
<div class="box">
<!-- input com ID duplicado -->
<input type="number" class="input"
data-testid="second-card-input-card-number" />
</div>
</div>
</div>

<div class="row">
<button role="button" class="button" data-testid="button-send">
<span class="children"> Send </span>
</button>
</div>

</div>

Exemplo 2

Removendo duplicidade em segundo nível de herança (contexto avô).

Síntese

...
container-payment-setup
├── container-left
| └── container-card
| └── left-card-input-card-number
├── container-right
| └── container-card
| └── right-card-input-card-number
...

HTML

<div class="this-is-my-area" data-testid="container-payment-setup">

<h3 class="title">Split payment</h3>

<div class="this-is-my-class" data-testid="container-left">
<div class="this-is-other-class" data-testid="container-card">
<div class="row">
<label class="label"> Card number </label>
<div class="box">
<!-- input com ID duplicado -->
<input type="number" class="input"
data-testid="left-card-input-card-number" />
</div>
</div>
</div>
</div>

<div class="this-is-my-class" data-testid="container-right">
<div class="this-is-other-class" data-testid="container-card">
<div class="row">
<label class="label"> Card number </label>
<div class="box">
<!-- input com ID duplicado -->
<input type="number" class="input"
data-testid="right-card-input-card-number" />
</div>
</div>
</div>
</div>

<div class="row">
<button role="button" class="button" data-testid="button-send">
<span class="children"> Send </span>
</button>
</div>

</div>

Exemplo 3

Removendo duplicidade de componentes com exibição de dados em lista.

Síntese

...
container-user-information
├── container-user-setup
| ├── container-row-0
| | ├── label-user-name
| | └── button-edit-user
| ├── container-row-1
| | ├── label-user-name
| | └── button-edit-user
| ├── container-row-2
| | ├── label-user-name
| | └── button-edit-user
...

HTML

<div class="this-is-my-area" data-testid="container-user-information">

<h3 class="title">Users information</h3>

<div class="this-is-my-class" data-testid="container-user-setup">

<div class="row" data-testid="container-row-0">
<!-- componentes em formato lista com ID duplicado -->
<label class="label" data-testid="label-user-name"> Ashok Kumar </label>
<button role="button" class="button" data-testid="button-edit-user">
<span class="children"> Edit </span>
</button>
</div>

<div class="row" data-testid="container-row-1">
<!-- componentes em formato lista com ID duplicado -->
<label class="label" data-testid="label-user-name"> Jane Doe </label>
<button role="button" class="button" data-testid="button-edit-user">
<span class="children"> Edit </span>
</button>
</div>

<div class="row" data-testid="container-row-2">
<!-- componentes em formato lista com ID duplicado -->
<label class="label" data-testid="label-user-name"> John Doe </label>
<button role="button" class="button" data-testid="button-edit-user">
<span class="children"> Edit </span>
</button>
</div>

</div>

</div>

Meus 50¢ e mais umas coisas importantes (eu acho)

Não é bala de prata por Giphy

Este guia foi criado para tornar a escrita e utilização de ID’s de teste durante o desenvolvimento de componentes de interface gráfica mais compreensível.

Embora algumas diretrizes apresentadas possam sugerir um modelo padronizado, o conteúdo aqui disposto foi elaborado com uma finalidade propositiva.

A intenção é promover uma abordagem sistemática e organizada ao trabalhar com esses recursos de desenvolvimento. Em vez de seguir um modelo padrão rígido, o guia foi concebido para oferecer uma solução eficiente e colaborativa para o tema.

Quanto ao processo de desenvolvimento

Práticas adequadas podem tornar o processo de desenvolvimento mais fluído e assertivo. A comunicação clara e eficaz é a chave para o sucesso nesse contexto.

Observando essa característica, é interessante manter a atenção em relação aos fatores que podem facilitar o fluxo de composição de componentes e a evolução do produto.

É imprescindível que o fluxo de composição seja revisitado desde o início por todos os envolvidos no processo.

O fluxo de composição observa todas as etapas do processo de desenvolvimento.

  • Especificação, prototipação, desenvolvimento e validação.

A atuação conjunta no fluxo de composição colabora com a viabilidade da entrega.

  • Promove a interação entre as áreas (negócio, design, desenvolvimento e demais interessados).
  • Facilita na compreensão dos objetivos e metas definidas.
  • Minimiza dúvidas logo nos estágios iniciais do processo.

Não esqueça de sanitizar seu código

O atributo data-testid é um recurso que colabora com o processo de desenvolvimento facilitando a elaboração dos testes de componentes de interface gráfica. Ele permite aos desenvolvedores identificar elementos HTML específicos na aplicação e anotá-los com informações adicionais que são úteis para os testes, mas não são exibidas para o usuário final.

É importante ressaltar que essa limpeza do atributo data-testid na versão produtiva do software colabora também com outros aspectos relacionados a qualidade, no qual podemos destacar:

  • Segurança - Sanitizar o atributo é importante para proteger a aplicação contra práticas que podem prejudicar a segurança dos dados e da aplicação como um todo, como por exemplo o web scraping.
  • Performance - Sanitizar o atributo pode melhorar a performance da aplicação pois evita que o código desnecessário prejudique a velocidade de carregamento da página.
  • Acessibilidade - Garantir que a aplicação seja acessível a todos os usuários, incluindo aqueles com deficiências, é fundamental para o sucesso de qualquer aplicação. Sanitizar o atributo pode evitar interferências no uso da aplicação por esses usuários.
  • Manutenibilidade - Sanitizar o atributo colabora para que os desenvolvedores trabalhem com uma estrutura de código limpa e organizada, tornando mais fácil o processo de análise do produto e a identificação de defeitos.

TL;DR

Utilizar o ID’s de teste em componente de interface gráfica de uma maneira sistemática e estruturada.

  • Componente é uma parte independente do frontend.
  • Componentes podem ser divididos em 2 grupos: primitivos e compostos.

O teste de componente considera exercitar algumas características.

  • Unidades do componente
  • Interações internas do componente
  • Services do componente

Utilize o atributo data-testid como identificador do componente.

  • Componente composto - utiliza identificador de escopo geral.
  • Componente primitivo - utiliza identificador de escopo específico.
  • Cada componente possui sua proposta para que seja criado.

Não esqueça das boas práticas.

  • Remova duplicidades quando necessário.
  • Sanitize os ID’s de teste da versão produtiva do software.
  • A comunicação clara e eficaz é a chave para o sucesso.

--

--