Lidando com testes mentirosos

Pinóquio

Um dos problemas que encontro com frequencia em equipes ágeis são os testes mentirosos. Testes mentirosos são aqueles que falham ocasionalmente mesmo quando não há nenhum problema com a aplicação sendo testada.

Algumas reações comuns são rodar o teste novamente ou comentá-lo.

O problema dos testes mentirosos é o mesmo da mentira, se ele disser a verdade um dia você pode não acreditar, quebrando o propósito do teste em questão. Grande parte do problema normalmente está nos testes de sistema, ou seja, os testes que precisam da aplicação de pé para funcionar.

Pirâmide alimentícia

Jason Huggins e Mike Cohn descreveram uma analogia da pirâmide alimentícia com os tipos e quantidade de testes que você deve ter. Na base da pirâmide alimentícia está o que você deve consumir mais e indo para o topo o que deve ser consumido menos. Isso é importante para uma dieta saudável. Pirâmide alimentícia

Trazendo para o software, a saúde de sua base de teste é em boa parte definida o quanto seu software tem de cada nível. Se o seu software comer do topo da pirâmide demais ele pode passar mal :).Explicando a pirâmide; na base estão os testes unitários. Testes unitários testam uma classe de forma isolada e não envovem o ambiente externo. Estes testes são executados muito rápido.

No meio estão os testes de integração. Testes de integração são aqueles testam uma ou várias classes em conjunto com seu ambiente (ex. testes que envolvem banco de dados, rede, etc) [1].

No topo da pirâmide estão os testes de sistema. Testes de sistemas são aqueles validam o sistema do ponto de vista do usuário e portanto precisam da aplicação e seu ambiente de pé para funcionarem (ex. testes baseados em interface). Eles devem compor a menor parte de sua base de teste.

Testes de sistema são problemáticos por natureza

A explicação para isso é que os testes de sistema são mais lentos, mais dependentes do ambiente e conseqüentemente mais instáveis. Como exemplo, podemos citar testes de interface de uma aplicação web. Alguns dos motivos para a fragilidade deste tipo de teste são:

  • Os testes de interface são mais sensíveis à alterações na aplicação que não mudam comportamento. Qualquer alteração na estrutura do HTML da página ou mensagens retornadas para o usuário pode interferir no resultado do teste.
  • A aplicação tem uma série de dependências como banco de dados, servidor de aplicação, rede. Qualquer falha em uma dessas dependências, seja problema de configuração ou comunicação faz com que o teste falhe.
  • A plataforma usada como meio de testes também contém bugs. Firefox, Internet Explorer, HtmlUnit, Windows travam ou tem bugs.

Minha recomendação geral é, como mostrado na pirâmide alimentícia, ter menos testes de sistema; o necessário para validar se as camadas conseguem se falar e verificar se a interface sabe lidar com as respostas da camada abaixo. As variações devem ser tratadas em outros tipos de teste, mais rápidos e melhores [2]. Essa estratégia de testes é essencial para um build rápido, item importante para a prática bem sucedida de integração contínua.

Tratando de código legado, transformar testes de sistema em testes de integração ou unidade pode ser desafiador, já que boa parte da lógica de negócio pode estar misturada com a lógica de interface (leia-se jsp de 2000 linhas), uma péssima prática em desenvolvimento de software [3].

Estabilizando os testes de sistema

Mesmo com menos testes de sistema, eles estarão lá e é importante que sejam confiáveis. Para alguns princípios sobre como estabilizá-los veja Lidando com testes mentirosos - Estabilizando os ofensores.

Complementos


[1] Tecnicamente os testes que envolvem várias classes, mesmo que não envolva o ambiente externo também são considerados testes de integração. Porém para o propósito deste post e por questõs práticas considero-os como testes de unidade.
[2] Para um excelente material que corrobora essa visão ver The Ongoing Revolution in Software Testing.
[3] Ver também humble objects