5 boas práticas para se aplicar em testes de aceitação

Publicado por Lindomar Reitz no dia qa

Testes de Aceitação

Aqui na Resultados Digitais damos muita atenção à qualidade do produto. Essa atenção se reflete, por exemplo, no uso de testes automatizados. Além dos testes de unidade e integração, os testes de aceitação merecem cuidado para evitar problemas de evolução e manutenção da suíte de testes.

Este artigo levanta alguns pontos sobre a arquitetura e boas práticas desse nível de testes.

Antes de começar, é importante relembrar o conceito de pirâmide de testes, definido previamente por Mike Cohn em seu livro Succeeding with Agile: Software Development Using Scrum:

Pirâmide de testes

A estratégia de testes deve ter como base os testes de unidade, seguidos por testes de serviço/integração e por fim, mas não menos importante, testes de aceitação.

Nesse último nível, os testes precisam refletir as situações de uso no sistema como um todo - contando inclusive com as suas dependências (banco de dados, APIs, etc). Apesar de aparecerem em menor quantidade, o código dos testes de aceitação deve ser tão bem cuidado quanto os testes de outros níveis.

Normalmente, os testes que se baseiam na interface gráfica tendem a sofrer mais mudanças do que o próprio comportamento do sistema - o que reforça a necessidade de ter uma arquitetura bem definida nesse nível.

Esse nível de teste tem como objetivo simular a navegação do usuário no sistema como, por exemplo, clicar em botões, selecionar opções, preencher campos, etc. É nessa etapa que temos mais certeza de que estamos entregando o que é esperado. Nesse momento, também validamos se todas as integrações (front end e back end) estão funcionando de acordo.

Boas práticas

A seguir, segue um pequeno resumo de boas práticas para os testes de aceitação.

1. Separe responsabilidades

Esse conceito é bastante utilizado no paradigma de orientação a objetos, principalmente para evitar códigos duplicados. Pegando como exemplo a documentação do Capybara, o código parece ser simples e bem intuitivo, mas não se deixe enganar!

O problema é escrever todo esse código no teste de aceitação, o que pode gerar dois tipos de duplicação: na mesma classe de teste ou em classes distintas. Além da duplicação envolvendo os detalhes de como se interage com o HTML da aplicação, também perde-se o foco do teste em si, pois devemos nos preocupar com o comportamento do sistema e não com os detalhes de implementação.

Com isso em mente, um padrão de testes bastante utilizado é o de Page Objects. Esse padrão tem o objetivo de separar a implementação da página em classes - encapsulando a forma de como um determinado framework interage com a aplicação. Como vantagem, uma vez que as mudanças serão aplicadas em um único ponto, temos um aumento na legibilidade dos testes e na facilidade na manutenção.

2. Dependa de elementos estáveis

Voltando ao exemplo do Capybara, outro problema comum é o de usar o texto como identificador do elemento no DOM (Document Object Model). Esse tipo de identificador é muito instável, pois o mesmo pode sofrer alterações ao longo do tempo, seja por um pedido do cliente, Product Owner ou redefinição de design, por exemplo. Outro problema é que caso a sua aplicação seja internacionalizada, o idioma nos testes será uma nova preocupação.

Para amenizar esse problema, dependa de elementos que contenham id e name, pois são identificadores em que uma mudança é mais difícil de ocorrer. Além de apresentarem um melhor desempenho na localização no DOM. Seletores CSS também são uma opção viável, porém mais passíveis de mudança.

Evite ao máximo utilizar XPath, pois esse é o tipo de seletor mais lento de ser encontrado e complicado de manter-se pois depende da posição das tags no HTML - o que é algo que também tende a mudar com o tempo.

3. Seja objetivo

Validar várias coisas na mesma página é um comportamento comum com quem começa a escrever testes automatizados. Essa atitude implica em testar várias situações em um único cenário. Um exemplo disso pode ser visto no código a seguir:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  def test_user_account
    account_page.fill_name 'John'
    account_page.fill_age 19
    account_page.click_on_register

    expect(account_page.get_message).to eq("User registered!")

    account_page.click_on_edit
    account_page.fill_age 20
    account_page.click_on_update

    expect(account_page.get_message).to eq("User updated!")

    account_page.click_on_delete
    account_page.click_on_confirm

    expect(account_page.get_message).to eq("User deleted!")

    # Outras situações ao longo do teste
  end

Esses testes são complicados de manter, pois, em caso de erro, fica difícil de identificar a causa raíz, aumentando o tempo de entendimento e resolução.

A dica é manter os testes com um objetivo bem claro, testando uma coisa de cada vez, começando por nomes significativos, com poucos passos e poucas validações. Um exemplo dessa refatoração é visto no código a seguir:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  def test_register_user
    account_page.fill_name 'John'
    account_page.fill_age 18
    account_page.click_on_register

    expect(account_page.get_message).to eq("User registered!")
  end

  def test_edit_user
    accounts_page.find_user 'Mary'
    account_page.click_on_edit
    account_page.fill_age 21
    account_page.click_on_update

    expect(account_page.get_message).to eq("User updated!")
  end

  def test_delete_user
    accounts_page.find_user 'Chris'
    account_page.click_on_delete
    account_page.click_on_confirm

    expect(account_page.get_message).to eq("User deleted!")
  end

4. Construa cenários independentes

Essa boa prática vai ao encontro com a anterior, pois um dos maiores problemas desse nível de teste é o alto tempo de execução se comparado com os testes de unidade e integração. Esse tempo se justifica pelo fato de que, além do tempo de requisição/resposta do servidor, é necessário abrir um browser e aguardar o carregamento das páginas.

Criar dependências entre cenários de testes parece ser uma boa ideia no inicio, já que traz uma “economia” de tempo na construção dos demais testes, porém pode trazer muito mais problemas do que soluções.

Com o aumento do número de cenários, fica cada vez mais complicado em manter uma ordem de execução, sendo que caso um teste do topo dessa hierarquia falhe, todos os demais também falharão, o que aumenta em muito o tempo de análise e resolução de erros.

A ideia é de construir cada cenário de forma isolada, montando a massa de dados de forma independente. Isso permite executar os testes em qualquer ordem e ainda oferece a possibilidade de executar os testes em paralelo, o que reduz drasticamente o tempo total de execução da sua suíte de testes.

5. Revise constantemente

Assim como o sistema está em constante evolução, os testes precisam ser acompanhados e revisados de perto, tanto por alterações das funcionalidades quanto pela utilidade do teste em si.

Não faz sentido manter testes de funcionalidades que foram removidas ou que se encaixam nos níveis inferiores da pirâmide de testes. Traduzindo: não tenha receio de apagar testes.

O que importa no final é que existam apenas testes que vão lhe trazer feedback na hora certa.

Conclusão

Testes automatizados são um dos pilares de um produto de qualidade, sendo que os testes de aceitação precisam ser tratados com tanto cuidado quanto os códigos de produção e dos demais níveis de teste. Com isso, tendo como objetivo o baixo custo de evolução e manutenção da suíte de testes, deve-se levar a sério a parte da arquitetura e boas práticas - já que esses testes serão mantidos por muito tempo.

Compartilhem conosco suas decisões de arquitetura e boas práticas nos comentários.

Leituras recomendadas

Lindomar Reitz

Lindomar Reitz

Quality Assurance

Comentários