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.
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:
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).
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.
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
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.
|