Três razões para abandonar esta prática e como resolvê-la

Trabalho com desenvolvimento de software com foco em testes há treze anos, sendo os últimos 6 anos e meio trabalhando com automação de testes de UI, e os últimos três anos e meio escrevendo código por mais de 50% de meu tempo no trabalho, tais como scripts de testes funcionais e testes de regressão visual, utilizando o framework Protractor.

Ao longo de minha carreira aprendi a escrever testes de UI de forma robusta, através do uso de boas práticas e da refatoração de testes para melhorá-los sempre que necessário.

Frameworks de automação de testes de UI como Protractor ou Selenium, para citar alguns, possuem métodos chamados sleep, os quais podem ser utilizados para ajudar na depuração de código, mas que devem ser evitados, senão até mesmo banidos, quando falando de scripts de testes que rodam em servidores de integração e entrega contínua, visto que tais testes devem ser confiáveis e gerar feedback real aos times de engenharia de software.

Neste post compartilho três razões pelas quais acredito que sleeps não devem ser utilizados em scripts de testes de UI.

1. Sleeps geram lentidão na execução dos testes

Normalmente, automatizadores de testes não experientes utilizam o articífio de colocar um sleep em um script de testes visto que sem tal sleep o testes as vezes falhariam, pois o próximo elemento com o qual se quer integragir ainda não estaria disponível na aplicação.

Segue um exemplo desta má prática utilizando o framework Protractor:

it("should see the 'Contact' title when clicking the contact link from the home page", () => {
    const titleElement = element(by.css("h2"));

    browser.get("https://example.com/");
    element(by.className("contact-link")).click();
    browser.sleep(3 * 1000);

    expect(titleElement.getText()).toEqual("Contact");
});

No exemplo acima, três segundos de sleep são adicionados entre o click em um link e a verificação de que o título da página é igual ao valor esperado.

O valor de três segundos pode ter sido definido da seguinte forma: O automatizador testou tal script sem sleep e o teste falhou, depois testou com um segundo de sleep e o teste falhou, então testou com dois segundos e o script continou a falhar, e quando ele testou com três segundos o teste passou.

Porém, dependendo de questões relacionadas a velocidade da internet ou disponibilidade do servidor, por exemplo, tal elemento pode as vezes estar disponível para a verificação antes dos três segundos, e visto que o script está explicitamente instruindo o testes a aguardar este tempo, o teste irá demorar mais do que necessário, tornando o feedback da execução dos testes lenta.

2. Sleeps geram resultados falsos negativos nos testes

Ainda utilizando o mesmo exemplo do item anterior, imagine agora o inverso. A internet na qual o servidor de integração contínua que executa tal teste está prejudicada. Neste caso, após visitar a página inicial da aplicação e clicar no link de contato o elemento h2 demora mais do que três segundos para ser exibido, fazendo o teste falhar com um possível resultado falso negativo, pois tal elemento talvez seria exibido após, digamos, cinco segundos.

Resultados falsos negativos em testes automatizados são prejudiciais e quando ocorrem com frequência fazem o time simplesmente não confiar mais nos testes, o que os torna ineficazes. É como na história do menino e o lobo.

Era uma vez um jovem pastor que costumava levar o seu rebanho de ovelhas para a serra a pastar. Como estava sozinho durante todo o dia, aborrecia-se muito. Então, pensou numa maneira de ter companhia e de se divertir um pouco. Voltou-se na direção da aldeia e gritou: “Lobo! Lobo!”. Os camponeses correram em seu auxílio. Não gostaram da graça, mas alguns deles acabaram por ficar junto do pastor por algum tempo. O rapaz ficou tão contente que repetiu várias vezes a façanha. Alguns dias depois, um lobo saiu da floresta e atacou o rebanho. O pastorzinho pediu ajuda, gritando ainda mais alto do que costumava fazer: “Lobo! Lobo!”. Como os camponeses já tinham sido enganados várias vezes, pensaram que era mais uma brincadeira e não o foram ajudar. O lobo pode encher a barriga à vontade porque ninguém o impediu. Quando regressou à aldeia, o rapaz queixou-se amargamente, mas o homem mais velho e sábio da aldeia respondeu-lhe: “Na boca do mentiroso, o certo é duvidoso.”

Ou seja, ninguém dará atenção quando tais testes estiverem falhando devido a bugs reais na aplicação, visto que estes são considerados flaky e o time não confia em seus resultados.

Já escrevi outros conteúdos sobre resultados falsos negativos. Segue o link para eles aqui caso queira se aprofundar no assunto.

3. Um sleep hoje é um convite para mais sleeps no futuro

Consideremos ainda o mesmo exemplo do primeiro item. Digamos que tal sleep tenha sido o primeiro adicionado na suite de testes automatizados de um determinado projeto. Em outro momento, outro automatizador de testes, também não muito experiente, precisa criar um novo teste e passa por um problema parecido, onde suas verificações falham com resultados falsos negativos pois o elemento com o qual o script tenta fazer a verificação ainda não está disponível. Ele procura em outros scripts de teste como tal problema foi resolvido e enfim encontra o exemplo com o sleep e, voilà! E assim mais um sleep é adicionado ao código, o qual, ou tornará os testes ainda mais lentos (item 1) quando o elemento estiver disponível antes do tempo definido, ou ainda, gerará resultados falsos negativos (item 2), em casos de internet lenta ou servidor sobrecarregado, por exemplo.

É como na teoria das janelas quebradas.

“Considere um edifício com algumas janelas quebradas. Se as janelas não forem reparadas, a tendência é que vândalos quebrem mais janelas. Após algum tempo, poderão entrar no edifício e, se ele estiver desocupado, torna-se uma “ocupação” ou até incendeiam o edifício.”

Ou seja, um sleep inocente em um caso de teste será um convite à outros, o que se torna uma “bola de neve” e faz com que os problemas citados nos itens 1 e 2 aconteçam cada vez com mais frequência, o que em certo momento tornará tal suite de testes insustentável e enfim descartada.

Soluções alternativas ao uso de sleeps

Boas práticas relacionadas a espera de elementos para posterior interação ou verificações estão disponíveis em frameworks de testes de UI como Selenium e Protractor.  Tais frameworks possuem funcionalidades tais como wait e ExpectedConditions.

A principal diferença entre um sleep e um wait é que o tempo definido na função sleep ocorre independente de o elemento com o qual se quer interagir estar ou não disponível antes desse tempo. Já o wait aguarda por no máximo o tempo definido nesta função, mas caso o elemento esteja disponível para interação antes desse tempo o teste segue para o próximo passo do script.

ExpectedConditions são utilizadas como condições passadas à função wait. Um exemplo é aguardar até que o elemento h2 (do exemplo do item 1 deste post) esteja visível e possua o texto ‘Contact’ antes de seguir adiante para o próximo passo do script, o qual verifica que o título da página é igual a string ‘Contact’.

Um conteúdo completo e bem explicado foi escrito há algum tempo pelo amigo e colega de profissão Stefan Teixeira, o qual explica de forma bem didática ambos wait e ExpectedConditions com Selenium e Java. A leitura é extremamente recomendada. Segue o link.

Para os usuários do Protractor, criei um node module chamado protractor-helper, o qual dentre outras funcionalidades possui algumas para aguardar por elementos estarem disponíveis antes de interagir com eles ou realizar verificações. Segue abaixo um exemplo de uso:

const protractorHelper = require("protractor-helper");

it("successful sign up", () => {
    browser.get("https://example.com/sign-up");

    const emailField = element(by.id("email"));
    const passwordField = element(by.id("password"));
    const signupButton = element(by.id("signup"));

    protractorHelper.fillFieldWithTextWhenVisible(emailField, "valid@email.com");
    protractorHelper.fillFieldWithTextWhenVisible(passwordField, "validpassword");
    protractorHelper.clickWhenClickable(signupButton);

    const avatar = element(by.id("avatar"));

    protractorHelper.waitForElementVisibility(avatar);

    expect(avatar.isDisplayed()).toBe(true);
});

Para mais informações leia o README do projeto.


Espero que tenha gostado do post e aguardo seus comentários. Bons testes!

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s