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 devem ser eliminados dos 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 teste 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 amanhã

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. Por exemplo, utilizando wait posso dizer: browser, aguarde por no máxmimo 10 segundos por determinado elemento ser exibido na tela. Se o elemento for exibido em 2 segundos, o teste vai para o próximo passo logo após estes 2 segundos, porém, se o elemento demorasse 5 segundos para ser exibido, o teste continuaria funcionando, seguindo para o próximo passo após os 5 segundos, e somente no caso de o elemento não estar disponível após 10 segundos o teste iria falhar, com um timeout após os 10 segundos.

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.

Infelizmente o conteúdo no Stefan não encontra-se mais disponível.

Para os usuários do Protractor, criei uma biblioteca chamada protractor-helper, a 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 helper = require("protractor-helper");

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

    const emailField = element(by.id("email"));
    const passwordField = element(by.id("password"));
    const signinButton = element(by.id("signin"));

    helper.fillFieldWithText(emailField, "valid@email.com");
    helper.fillFieldWithText(passwordField, "validpassword");
    helper.click(signinButton);

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

    protractorHelper.waitForElementVisibility(avatar);

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

No exemplo acima, nas acões de preenchimento do formulário de login, e no clique, implicitamente o protractor-helper irá aguardar por no máximo 5 segundos antes de falhar o teste, porém, se tais elementos estiverem disponíveis antes desse tempo, ele então fará a interação. Ou seja, digamos que o campo de email, senha e botão de login estejam disponíveis após 2 segundos, este é o tempo máximo que as funções fillFieldWithText e click irão aguardar.

Ainda no exemplo acima, a funcão waitForElementVisibility também aguarda por no máximo 5 segundos antes de falhar o teste, porém, se o avatar aparecer antes disso, a verificação é realizada imadiatamente.

Para mais informações leia o README do projeto, agora também disponível em Português.


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

5 comentários em “Por que não se deve utilizar sleeps em testes automatizados

  1. MUito bom… a cada dia que passa aumenta minha vontade de automatizar com protractor… Já não gosto de usar sleeps, após este artigo é uma pratica que deixarei de lado.

Deixe um comentário