Diretriz: Suíte de Teste
Está diretriz discute como manter suítes de teste automatizadas.
Relacionamentos
Elementos Relacionados
Descrição Principal

Introdução

A suíte de teste provê uma maneira de gerenciar a complexidade da implementação dos testes. Muitos esforços de teste de sistema falham porque o time se perde na minúcia de todos os testes detalhados, e subseqüentemente perde o controle do esforço de teste. Similar a pacotes UML, suítes de testes provêm uma hierarquia de containeres de encapsulamento para ajudar a gerenciar a implementação do teste. Provêem uma maneira de gerenciar os aspectos estratégicos do esforço de teste reunindo testes em grupos relacionados que podem ser planejados, gerenciados, e avaliados de uma maneira significante.

A suíte de teste representa um container para organização arbitrária de coleções de scripts teste relacionados. Isto também pode ser feito (implementado) como uma ou mais suítes de teste de regressão automatizados, mas a suíte de teste pode também funcionar como um plano para implementação de um grupo de scripts de teste manual relacionados. Perceba também que suítes de testes podem aninhadas hierarquicamente, então uma suíte de teste pode ser enclausurada dentro de outra.

Algumas vezes estes grupos de teste serão relacionados diretamente a algum subsistema ou outro elemento de design, mas outras vezes elas serão relacionadas diretamente a coisas como dimensões de qualidade, funções centrais de "missão crítica", conformidade com requisitos, aderência a padrões, e muitas outras preocupações que são organizadas baseadas em requisitos e então navegam entre os elementos internos do sistema.

Considere criar suítes de teste que organize os scripts de teste disponíveis, em acréscimo a outras suítes de teste, em muitas combinações diferentes: quanto mais variações você tiver, mais você vai aumentar a cobertura e o potencial para encontrar erros. Imagine uma variedade de suítes de teste que irá cobrir de forma completa e profundamente os itens do objetivo do teste. Lembre-se da devida implicação que único script de teste (ou suíte de testes) pode aparecer em muitas suítes de teste diferentes.

Algumas ferramentas de automação provêem à funcionalidade de automaticamente gerar ou montar uma suíte de testes. Há também técnicas de implementação que permite suítes de teste automatizadas dinamicamente selecionar todos ou parte de seus componentes scripts de teste para cada ciclo de execução de teste.

Suítes de testes podem lhe ajudar a manter seus componentes de teste e impor um nível de organização que facilita todo o esforço de teste. Como objetos físicos, testes podem quebrar. Não é porque eles se desgastaram, é porque alguma coisa mudou em seu ambiente. Talvez eles tenham sido portados para um novo sistema operacional. Ou, provavelmente, o código que eles exercitam mudou de um modo que oportunamente provocou a falha do teste. Suponha que você está trabalhando na versão 2.0 de uma aplicação de e-banking. Na versão 1.0, este método era usado para fazer login:

public boolean login (String username);

Na versão 2.0, o departamento de marketing percebeu que proteção por senha poderia ser uma boa idéia. Então o método foi alterado para:

public boolean login (String username, String password);

Qualquer teste que usava a primeira forma de login irá falhar. Nem mesmo irá compilar. Neste exemplo você pode descobrir que, poucos testes relevantes podem ser escritos sem usar o login. Você pode deparar-se com centenas ou milhares de testes falhando.

Estes testes podem ser consertados usando uma ferramenta global de busca-e-substituição que encontra toda instancia de login(something) e substitui com login(something, "senha de mentira"). Então arrume para que todas as contas de teste utilizem esta senha, e então você poderá seguir em frete.

Então, quando marketing decidir que a senhas não devem conter espaços em branco, você terá de fazer tudo de novo.

Este tipo de coisa é demasiado sobrecarregante, especialmente quando, e como é na maior parte dos casos, as mudanças no teste não são tão fáceis de se fazer. Existe uma maneira melhor.

Suponha que o script originalmente não chame o método de login de produtos. Melhor, eles chamam um método de uma biblioteca que faz, o que o que for necessário, para fazer o teste fazer o login e ficar pronto para proseguir. Inicialmente, este método pode ser parecido com:

public boolean testLogin (String username) {
  return product.login(username);
}

Quando as mudanças na versão 2.0 aconteceram, a biblioteca útil é alterada para corresponder à:

public Boolean testLogin (String username) {
  return product.login(username, "dummy password");
}

Em vez de alterar mil testes, você altera um método.

Em condições ideais, todas as bibliotecas de métodos necessários devem estar disponíveis no inicio do esforço de teste. Na prática, elas não podem ser antecipadas, talvez você não perceba que você precisa de um método útil testLogin até a primeira vez que o login de produtos mude. Deste modo métodos úteis de testes são frequentemente fatorados de necessidades de testes existentes na medida em que forem surgindo. É muito importante que você execute este reparo progressivo do teste, mesmo sobre pressão do cronograma. Se você não o fizer, você vai gastar muito tempo lidando com uma suíte de testes feia e não passível de manutenção. Talvez você ache melhor descartá-la, ou se sinta incapacitado de escrever o numero necessário de novos testes porque todo o seu tempo disponível está sendo gasto na manutenção de testes antigos.

Nota: Os testes do método de login de produtos ainda irão chamá-lo diretamente. Se o seu comportamento muda, alguns ou todos estes testes vão precisar ser atualizados. (Se nenhum dos testes de login falhar quando o seu comportamento muda, eles provavelmente não são muito bons em detectar defeitos.)

Abstração ajuda a lidar com a complexidade

O exemplo anterior mostrou como testes podem ser abstraídos da aplicação concreta. Provavelmente você pode fazer muito mais abstração. Talvez você possa descobrir que o numero de testes iniciando com uma seqüência comum de chamada de métodos: eles se logam no sistema, fixam algum estado, e navegam para a parte da aplicação que você está testando. Somente quando, cada teste faz alguma coisa diferente. Toda esta configuração pode e deve ser contida em um único método com um nome de invocação como readyAccountForWireTransfer. Fazendo isto, você está ganhando um tempo considerável quando novos testes de um tipo particular são escritos, e você também está fazendo o objetivo de cada teste muito mais inteligível.

Testes inteligíveis são importantes. Um problema comum com suítes de teste antigas é que ninguém entende o que o teste está fazendo ou por que. Quando eles se quebram, a tendência é arrumá-los na maneira mais simples possível. Isto muitas vezes resulta em testes fracos em encontrar erros. Eles não mais testam o que originalmente tinham o objetivo de testar.

Descartando testes

Mesmo com bibliotecas úteis, um teste pode periodicamente se quebrar por mudanças de comportamento que não tem nada a ver com o que ele verifica. Arrumar o teste não se limita à possibilidade de encontrar o defeito devido à mudança; é algo que você faz para preservar a possibilidade do teste encontrar algum outro defeito algum dia. Mas o custo de tais séries de consertos pode exceder o valor dos testes hipoteticamente encontrar um defeito. Pode ser melhor simplesmente descartar o teste e dedicar o esforço em criar novos testes com importância maior.

A maior parte das pessoas resiste em descartar teste, pelo menos até que eles estejam tão estupefatos pela carga de manutenção que eles descartam os testes. É melhor fazer a decisão cuidadosamente e continuamente, teste por teste, perguntando:

  • Quanto trabalho será gasto para deixar estes testes em boas condições, talvez adicionando a biblioteca útil?
  • De que outra maneira o tempo poderia ser usado?
  • Qual é a possibilidade do teste no futuro encontrar defeitos sérios? Qual tem sido o recorde dele e de seus testes relacionados?
  • Quanto tempo irá se passar até que ele se quebre de novo?

As respostas para estas questões serão difíceis de estimar o mesmo chutar. Mas fazê-las trará mais resultado que simplesmente ter a política de arrumar todos os testes.

Outro motivo para descartar testes é que eles ficam redundantes. Por exemplo, anteriormente no desenvolvimento, pode ter existido uma grande quantidade de testes simples dos métodos da construção de uma arvore de parse básica (o método LessOp e seus semelhantes). Posteriormente, durante a codificação do parser, existirá um bom número de testes do parser. Desde que o parser usa os métodos da construção, o parser irá indiretamente testá-los. Na medida em que mudanças no código quebram os testes da construção, é razoável descartar alguns deles por ficarem redundantes. É claro, qualquer comportamento novo ou modificado na construção irá precisar de novos testes. Eles podem ser implementados diretamente (se são difíceis de testar através do parser) ou indiretamente (se os testes através do parser são suficientes e mais fáceis de fazer manutenção).