Conceito: Catálogo de Ideias de Teste
Relacionamentos
Elementos Relacionados
Descrição Principal

Introdução

Grande parte da programação consiste em usar as coisas que você já usou várias vezes antes e usá-las, mais uma vez, em um contexto diferente. Essas coisas são normalmente certas estruturas de classes de dados (tais como listas encadeadas, tabelas hash ou bancos de dados relacionais) ou operações (tais como pesquisar, ordenar, criar arquivos temporários ou mostrar uma janela do navegador). Por exemplo, dois bancos de dados relacionais de clientes terão muitas características em estereotipadas.

A coisa interessante sobre esses estereótipos é que eles têm falhas estereotipadas. As pessoas não inventam novas formas imaginativas para inserir incorretamente alguma coisa em uma lista duplamente encadeada. Elas tendem a cometer os mesmos erros que cometeram anteriormente. Um programador que mostra uma janela do navegador pode cometer um desses erros estereotipados:

  • criar uma nova janela quando uma, que já estava aberta, deveria ser reutilizada
  • deixar de tornar visível uma janela do navegador minimizada ou obscurecida
  • usar o Internet Explorer quando o usuário escolheu outro navegador como padrão
  • não verificar se o JavaScript está ativado

Uma vez que as falhas são estereotipadas, são as ideias de teste que podem encontrá-las. Coloque estas ideias de teste em seu catálogo para que você possa reutilizá-las.

Como um Catálogo de Ideias de Teste Encontra Falhas

Uma das virtudes de um catálogo é que uma única ideia de teste pode ser útil para encontrar mais de uma falha oculta. Veja um exemplo de uma ideia que encontra duas falhas.

A primeira falha aconteceu em um compilador C. Este compilador aceita as opções de linha de comando "-table" ou "-trace" ou "-nolink". As opções podem ser abreviadas para as suas menores formas únicas. Por exemplo, "-ta" é o mesmo que "-table". Entretanto, "-t" não é permitido, porque é ambíguo: pode significar tanto "-table" como "-trace".

Internamente, as opções de linha de comando são armazenadas em uma tabela como esta:

-table
-trace
-nolink

Quando uma opção é encontrada na linha de comando, ela é procurada na tabela. Ela tem correspondência se tiver o mesmo prefixo de qualquer entrada da tabela, isto é, "-t" corresponde a "-table". Após uma correspondência ter sido encontrada, o resto da tabela é pesquisado para encontrar outra correspondência. Outra correspondência seria um erro, porque iria indicar uma ambiguidade.

O código que faz a pesquisa parece com este:

Você vê o problema? É bastante sutil.

O problema está na declaração break. A intenção é parar e sair do laço mais externo quando uma correspondência duplicada for encontrada, mas ele só sai do laço interno. Isto tem o mesmo efeito de não encontrar uma segunda correspondência: o índice da primeira correspondência é retornado.

Note que esta falha só pode ser encontrada se a opção for procurada por duas correspondências na tabela, tal como "-t" iria.

Agora vamos analisar outra falha completamente diferente.

O código obtém uma string. Supõe-se que irá substituir o último '=' na string por um '+'. Se não houver nenhum '=', nada será feito. O código usa a rotina strchr da biblioteca C padrão para encontrar a localização de '='. Aqui está o código:

Este problema também é um tanto sutil.

A função strchr retorna a primeira correspondência na string, e não a última. A função correta é strrchr. O problema é provavelmente um erro tipográfico. (Na verdade, o problema oculto é que é definitivamente desaconselhável colocar duas funções cujos nomes diferem somente por um caractere em uma biblioteca padrão.)

Este erro só pode ser encontrado quando houver dois ou mais sinais de igual na entrada. Isto é:

  • "a=b" retornaria o resultado correto, "a+b".
  • "noequals" iria retornar o resultado correto, "noequals".
  • "a=b=c" iria retornar incorretamente "a+b=c", e não o correto "a=b+c"

O que há de interessante e útil aqui é que temos duas falhas com causas completamente diferentes (erro tipográfico, equívoco de uma construção C), e as diferentes manifestações no código (chamada à função errada, declaração mal usada ou incompleta) que podem ser encontradas pela mesma ideia de teste (busca de algo que ocorre duas vezes).

Um Bom Catálogo de Ideias de Teste

O que torna um catálogo bom?

  • Conter um pequeno conjunto de ideias de teste que possam encontrar um conjunto muito maior de falhas ocultas.
  • Seja fácil de ler rapidamente. Permitir que seja possível ignorar as ideias de teste que não são relevantes para a sua situação.
  • Conter somente as ideias de teste que você irá usar. Por exemplo, alguém que nunca irá lidar com navegadores web não deveria ter que lidar com as ideias de teste para programas que usam navegadores web. Alguém que trabalhe com software de jogos irá querer um catálogo mais curto do que alguém que trabalhe com software de segurança crítica. A pessoa dos jogos pode se dar ao luxo de concentrar-se apenas nas ideias de teste que têm maior chance de encontrar falhas.

Dadas essas regras, parece ser melhor ter mais do que um catálogo. Alguns dados e operações são comuns a todas as linguagens, sendo assim, as suas ideias de teste podem ser colocadas em um catálogo que todos os programadores possam usar. Outros são específicos para um determinado domínio, então as ideias de teste para eles podem ser colocadas em um catálogo de ideias teste de domínio específico.

O catálogo de exemplo (tstids_short-catalog.pdf)(Obtenha o Adobe Reader), usado no exemplo a seguir, é uma boa opção para começar. Ideias de Teste para Misturas de Es e OUs é outro exemplo.

Um Exemplo de Uso de um Catálogo de Ideias de Teste

Veja aqui como você pode usar o catálogo de exemplo (tstids_short-catalog.pdf)(Obtenha o Acrobat Reader). Suponha que você esteja implementando este método:

applyToCommonFiles tem dois diretórios como argumentos. Quando um arquivo no primeiro diretório tiver o mesmo nome de um arquivo, no segundo, applytocommonfiles realiza algumas operações nos dois arquivos. Ele desce nos subdiretórios.

O método para usar o catálogo é através da varredura à procura dos segmentos que correspondam a sua situação. Considere as ideias de teste em cada segmento e veja se elas são pertinentes e, então, escreva aquelas que são relevantes em uma Lista de Ideias de Teste.

Nota: Esta descrição passo-a-passo pode fazer parecer que o uso do catálogo seja trabalhoso. Leva mais tempo para ler sobre a criação da lista de verificação do que para realmente criar uma.

Sendo assim, no caso de applyToCommonFiles, você pode aplicar o catálogo da forma descrita nesta seção.

A primeira entrada é para Qualquer Objeto. Qualquer um dos argumentos poderia ser um ponteiro nulo? Esta é uma questão do contrato entre applyToCommonFiles e seus invocadores. O contrato pode ser tal que os invocadores não passem um ponteiro nulo. Se eles fizerem isso, você não poderá confiar no comportamento esperado: applyToCommonFiles poderia executar qualquer ação. Neste caso, nenhum teste é apropriado, visto que qualquer coisa que applyToCommonFiles fizer não estará errado. Se, entretanto, for exigido que applyToCommonFiles verifique se há ponteiros nulos, a ideia de teste seria útil. Vamos assumir a última, que nos dá esta lista inicial de ideias de teste:

  • d1 é nulo (caso de erro)
  • d2 é nulo (caso de erro)
  • op é nulo (caso de erro)

A próxima entrada do catálogo é Strings. Os nomes dos arquivos são strings, e são comparados para ver se correspondem. A ideia de testar com a strings vazia ("") não parece útil. Presumivelmente algumas rotinas padrão de comparação de string serão usadas, e elas irão tratar as strings vazias corretamente.

Mas espere... Se existem strings sendo comparadas, o que acontece com Maiúsculas e Minúsculas? Suponha que d1 contenha um arquivo chamado "Arquivo" e d2 contenha um arquivo chamado "arquivo". Esses arquivos correspondem? No Unix, certamente que não. No Microsoft® Windows®, eles certamente iriam. Outra ideia de teste:

  • Os arquivos correspondem nos dois diretórios, mas a caixa dos nomes é diferente.

Note que esta ideia de teste não veio diretamente do catálogo. Entretanto, o catálogo chamou a nossa atenção para um aspecto específico do programa (nomes de arquivos como strings), e a nossa criatividade nos deu uma ideia adicional. É importante não usar o catálogo restritivamente - use-o como uma técnica para brainstorming, uma forma de inspirar novas ideias.

A próxima entrada é Coleções. Um diretório é uma coleção de arquivos. Muitos programas que tratam coleções falham quando elas estão vazias. Alguns que tratam as coleções vazias, ou com muitos elementos, falham nas coleções com exatamente um elemento. Então estas ideias são úteis:

  • d1 está vazio
  • d2 está vazio
  • d1 tem somente um arquivo
  • d2 tem somente um arquivo

A próxima ideia é usar uma coleção do tamanho máximo possível. Isto é útil porque programas como applyToCommonFiles muitas vezes são testados com poucos diretórios comuns. Então, alguns usuários usam-nos com duas enormes árvores de diretório com milhares de arquivos em cada uma, apenas para descobrir que o programa é grotescamente ineficiente com a memória e não pode tratar este caso realista.

Agora, testar o tamanho máximo absoluto para um diretório não é importante, só precisa ser grande o bastante para que um usuário possa usá-lo. Entretanto, deve existir pelo menos algum teste com mais de três arquivos em um diretório:

  • d1 contém muitos arquivos
  • d2 contém muitos arquivos

A ideia de teste final (elementos duplicados) não se aplica aos diretórios de arquivos. Isto é, se você tiver um diretório com dois arquivos que tenham o mesmo nome, você tem um problema independente de applyToCommonFiles - o sistema de arquivos está corrompido.

A próxima entrada do catálogo é Pesquisa. Essas ideias podem ser traduzidas em termos de applyToCommonFiles como estas:

  • d1 e d2 não têm arquivos em comum (todos os nomes são diferentes)
  • d1 e d2 têm exatamente um arquivo em comum (é alfabeticamente o último elemento do diretório)
  • d1 e d2 têm mais de um arquivo em comum

A ideia de teste final verifica se applyToCommonFiles termina muito cedo. Ele retorna assim que encontra a primeira correspondência? A observação entre parênteses na ideia de teste anterior pressupõe que o programa irá buscar a lista de arquivos em um diretório usando alguma rotina da biblioteca que os retorne ordenados alfabeticamente. Se não estiver, será melhor encontrar qual é o último realmente (o criado mais recentemente?) e fazer com que ele seja o elemento de correspondência. Antes de dedicar muito tempo para descobrir como os arquivos estão ordenados, pergunte-se se colocar o elemento a ser correspondido por último, irá tornar mais fácil a descoberta de defeitos. Colocar um elemento na última posição de uma coleção é mais útil se o código manipula a coleção usando um índice. Se estiver usando um iterator, é extremamente improvável que a ordem tenha importância.

Vamos analisar mais uma entrada no catálogo de exemplo. A entrada Estruturas Ligadas lembra-nos que estamos comparando árvores de diretório, e não apenas coleções de arquivos. Seria triste se applyToCommonFiles trabalhasse apenas nos diretórios de nível superior, e não nos de níveis inferiores. Decidir a forma de testar se applyToCommonFiles trabalha em diretórios de níveis mais baixos nos obriga a enfrentar a incompletude da sua descrição.

Em primeiro lugar, quando applyToCommonFiles desce nos subdiretórios? Se a estrutura de diretórios tiver esta aparência

Figura 1: Estrutura de diretório

será que applyToCommonFiles desce até Cdir? Isso parece não fazer sentido. Pode não existir correspondência na outra árvore de diretórios. Na verdade, parece que os arquivos nos subdiretórios só podem corresponder se os nomes dos subdiretórios corresponderem. Ou seja, suponha que temos esta estrutura de diretório:

Figura 2: Uma segunda estrutura de diretório

Os arquivos chamados "File" não correspondem, porque eles estão em subdiretórios diferentes. Os subdiretórios devem descender apenas se tiverem o mesmo nome em d1 e d2. O que leva a estas ideias de teste:

  • Algum subdiretório em d1 não é encontrado em d2 (sem descendência).
  • Algum subdiretório em d2 não é encontrado em d1 (sem descendência)
  • Algum subdiretório aparece tanto em d1 como em d2 (descende)

Mas isso levanta outras questões. A operação (op) pode ser aplicada para encontrar subdiretórios ou apenas arquivos? Se for aplicada aos subdiretórios, ela deve ser aplicada antes ou depois da descendência? Isso faz diferença, se, por exemplo, a operação apagar os arquivos ou diretórios correspondentes. E em assim sendo, a operação deve ser autorizada a modificar a estrutura de diretórios? e, mais especificamente: qual é o comportamento correto de applyToCommonFiles se isso acontecer? (este é o mesmo problema que surge com iteradores.)

Estes tipos de dúvidas surgem normalmente quando você lê atentamente uma descrição da criação de ideias de teste para o método. Mas vamos deixá-las de lado por enquanto. Quaisquer que sejam as dúvidas, deverão existir ideias de teste para elas - ideias de teste que verifiquem se o código as implementa corretamente.

Vamos voltar ao catálogo. Nós ainda não consideramos todas as ideias de teste. A primeira - vazia (nada na estrutura) - pergunta por um diretório vazio. Nós já obtivemos isso no item Coleções. Nós também já obtivemos a estrutura mínima não-vazia, que é um diretório com um único elemento. Este tipo de redundância não é raro, mas é fácil de ignorar.

E a estrutura circular? As estruturas de diretório não podem ser circulares - um diretório não pode estar dentro de um de seus descendentes ou dentro de si mesmo... ou pode? E os atalhos (no Windows) ou links simbólicos (no UNIX)? Se houver um atalho na árvore de diretório em d1 que aponta de volta para d1, applyToCommonFiles deve continuar descendo indeterminadamente? A resposta pode levar a uma ou mais ideias de teste novas:

  • d1 é circular por causa de atalhos ou links simbólicos
  • d2 é circular por causa de atalhos ou links simbólicos

Dependendo do comportamento correto, podem existir mais ideias de teste além desta.

Finalmente, e a profundidade maior que um? As primeiras ideias de teste irão garantir que estamos testando em um nível descendente de subdiretório, mas devemos verificar se applyToCommonFiles continua descendo:

  • descendo diversos níveis (>1) dos subdiretórios em d1
  • descendo diversos níveis (>1) dos subdiretórios em d2

Criando e Mantendo Seu Próprio Catálogo de Ideias de Teste

Como mencionado anteriormente, os catálogos genéricos não conterão todas as ideias de teste que você precisa. Mas, os catálogos de domínios específicos não têm sido publicados fora das empresas que os criaram. Se você os quiser, precisa construí-los. Eis alguns conselhos.

  • Não encha o catálogo com especulações sobre quais ideias poderiam ser boas para encontrar falhas. lembre-se que cada ideia de teste que você coloca no catálogo custa tempo e dinheiro:
    • Seu tempo para manter o catálogo
    • O tempo de outros programadores para pensar sobre a ideia de teste
    • Possivelmente, o tempo de outros programadores para implementar um teste

    Adicione apenas as ideias que têm demonstrado um registro histórico. Você deve ser capaz de identificar, pelo menos, uma falha real que a ideia de teste tenha capturado. Idealmente, a falha deverá ser aquela que não foi detectada por outros testes, isto é, uma que foi relatada quando da execução. Uma boa forma para construir catálogos é navegar no banco de dados de falhas da sua empresa e questionar como cada uma poderia ter sido detectada mais cedo.


  • É pouco provável que o trabalho de criar e manter um catálogo de ideias de teste é algo que possa ser feito em seu tempo livre. Você precisará alocar um tempo especifico para esta tarefa, tal como para qualquer outra importante. Nós recomendamos que você crie e mantenha o seu Catálogo de Ideias de Teste durante o Detalhe de Fluxo de Trabalho: Melhorar os Recursos de Teste.