Diretrizes:
|
publicIsClear |
technicianClear |
O código escrito... |
O código correto... |
|
verdadeiro |
verdadeiro |
detona |
teria detonado |
o teste é inútil (para esse erro) |
verdadeiro |
falso |
detona |
não teria detonado |
teste útil |
falso |
verdadeiro |
detona |
não teria detonado |
teste útil |
falso |
falso |
não detona |
não teria detonado |
o teste é inútil (para esse erro) |
Os dois testes intermediários são úteis para localizar esse erro específico. Entretanto, observe que são redundantes: como qualquer um deles localizará o erro, não será necessário executar os dois.
A expressão poderia apresentar outros erros. Abaixo são apresentadas duas listas de erros comuns em boolean expressions. Os erros à esquerda são todos detectados pela técnica abordada aqui. Os erros à direita talvez não sejam. Assim, essa técnica não detecta todos os erros desejados, mas mesmo assim é útil.
Erros detectados |
Erros possivelmente não detectados |
| Uso do operador incorreto: a||b deveria ser a&&b | Uso da variável incorreta: a&&b&&c deveria ser a&&x&&d |
| Negação omitida ou incorreta: a||b deveria ser !a||b ou !a||b deveria ser a||b | Expressão muito simples: a&&b deveria ser a&&b&&c |
| Ausência de parênteses na expressão: a&&b||c deveria ser a&&(b||c) | Expressões com mais de um dos erros na coluna da esquerda |
| Expressão extremamente complexa: a&&b&&c deveria ser a&&b (Esse erro não é muito provável, mas pode ser localizado facilmente com testes úteis por outros motivos.) |
Como essas idéias são usadas? Suponha que você receba uma boolean expression como a&&!b. Você poderia criar uma tabela real como esta:
a |
b |
a&&!b |
talvez devesse ser |
talvez devesse ser |
talvez devesse ser |
... |
verdadeiro |
verdadeiro |
falso |
verdadeiro |
falso |
verdadeiro |
... |
verdadeiro |
falso |
verdadeiro |
verdadeiro |
falso |
falso |
... |
falso |
verdadeiro |
falso |
falso |
falso |
falso |
... |
falso |
falso |
falso |
verdadeiro |
verdadeiro |
falso |
... |
Se investigar todas as possibilidades, você descobrirá que a primeira, a segunda e a quarta possibilidade são suficientes. A terceira expressão não localizará erros que não seriam localizados por uma das outras. Assim, não é necessário testá-la. (À medida que as expressões se tornam mais complexas, as economias decorrentes de casos desnecessários aumentam rapidamente.)
Evidentemente, uma pessoa sensata não criaria esse tipo de tabela. Felizmente, você não precisará criá-la. É fácil memorizar os casos necessários para expressões simples. (Consulte a próxima seção.) Para expressões mais complexas, como A&&B||C, consulte Idéias de Teste para Combinações de ANDs e ORs, que lista idéias de teste para expressões com dois ou três operadores. Para expressões ainda mais complexas, é possível usar um programa para gerar idéias de teste.
Se a expressão for A&&B, teste com:
A |
B |
verdadeiro |
verdadeiro |
verdadeiro |
falso |
falso |
verdadeiro |
Se a expressão for A||B, teste com:
A |
B |
verdadeiro |
falso |
falso |
verdadeiro |
falso |
falso |
Se a expressão for A1 && A2 && ... && An, teste com:
A1, A2, ... e An são todos verdadeiros |
A1 é falso, todo o resto é verdadeiro |
A2 é falso, todo o resto é verdadeiro |
... |
An é falso, todo o resto é verdadeiro |
Se a expressão for A1 || A2 || ... || An, teste com:
A1, A2, ... e An são todos falsos |
A1 é verdadeiro, todo o resto é falso |
A2 é verdadeiro, todo o resto é falso |
... |
An é verdadeiro, todo o resto é falso |
Se a expressão for A, teste com:
A |
verdadeiro |
falso |
Assim, quando precisar testar a&&!b, você poderá aplicar a primeira tabela acima, inverter o sentido de b (porque é negado) e obter esta lista de Idéias de Teste:
Um outro exemplo de código com erro é:
if (finished < required) {
siren.sound();
}
O sinal de < deveria ser <=. Esses erros são bastante comuns. Da mesma forma que com boolean expressions, você pode criar uma tabela de valores de teste e ver quais detectam o erro:
finished |
required |
o código escrito... |
o código correto... |
1 |
5 |
faz soar a sirene |
teria feito soar a sirene |
5 |
5 |
não faz soar a sirene |
teria feito soar a sirene |
5 |
1 |
não faz soar a sirene |
não teria feito soar a sirene |
Em termos mais gerais, o erro poderá ser detectado sempre que finished=required. Com base em análises de erros plausíveis, podemos obter estas regras para idéias de teste:
Se a expressão for A<B ou A>=B, teste com
A=B |
A ligeiramente menor que B |
Se a expressão for A>B ou A<=B, teste com
A=B |
A ligeiramente maior que B |
O que significa "ligeiramente "? Se A e B forem números inteiros, A deverá ser uma unidade menor ou maior que B. Se forem números de ponto flutuante, A deverá ser um número bem próximo a B. (Provavelmente não é necessário que seja o número de ponto flutuante mais próximo de B.)
A maioria dos operadores relacionais ocorre em boolean expressions, como neste exemplo:
if (finished < required) {
siren.sound();
}
As regras para expressões relacionais resultariam nestas idéias de teste:
As regras para boolean expressions resultariam nestas idéias de teste:
Mas se finished for ligeiramente menor que required, finished < required será verdadeiro, logo, não terá sentido escrever a última expressão.
E se finished for igual a required, finished < required será falso, logo, também não terá sentido escrever a última expressão.
Assim, se uma expressão relacional não contiver operadores booleans (&& e ||), ignore o fato de que ela é também uma boolean expression.
Os fatos são um pouco mais complicados com combinações de operadores booleans e relacionais, como esta:
if (count<5 || always) {
siren.sound();
}
A partir da expressão relacional, observa-se que:
A partir da boolean expression, observa-se que:
Essas expressões podem ser combinadas em três idéias de teste mais específicas. (Aqui, observe que count é um número inteiro.)
Observe que count=5 é usado duas vezes. Talvez seja melhor usá-lo somente uma vez, para permitir o uso de algum outro valor afinal, por que testar count com 5 duas vezes? Não seria melhor testar uma vez com 5 e outra vez com algum outro valor de modo que count<5 seja falso? Seria, mas é perigoso tentar porque é fácil cometer um erro. Suponha que você tentasse o seguinte:
Suponha que haja um erro que só possa ser detectado com count=5. Isso significa que o valor 5 fará com que count<5 produza falso no segundo teste, quando o código correto teria produzido verdadeiro. No entanto, imediatamente é feito um OR desse valor falso com o valor de always, que é verdadeiro. Isso significa que o valor de toda a expressão está correto, ainda que o valor da subexpressão relacional esteja incorreto. O erro passará despercebido.
O erro não passará despercebido se o outro count=5 não for tão específico.
Problemas semelhantes ocorrerão quando a expressão relacional estiver do lado direito do operador boolean.
Como é difícil saber quais subexpressões devem ser exatas e quais podem ser gerais, convém tornar todas elas exatas. A alternativa é usar o programa de boolean expression mencionado acima. Ele produz idéias de teste corretas para uma combinação arbitrária de boolean expressions e expressões relacionais.
Conforme explicado em Conceitos: Teste Anterior ao Design, geralmente é preferível projetar os testes antes de implementar o código. Dessa forma, ainda que as técnicas sejam motivadas por exemplos de código, geralmente elas serão aplicadas sem código. Como?
Determinados artefatos de design, como diagramas de estados e diagramas de seqüência, usam boolean expressions como guardas. Esses casos são diretos; basta adicionar as idéias de teste das boolean expressions à lista de verificação de idéias de teste do artefato. Consulte Diretrizes: Idéias de Teste para Diagramas de Estados e de Atividades.
O caso mais complicado é quando as boolean expressions são implícitas em vez de explícitas. Geralmente, isso ocorre em descrições de APIs. A seguir é apresentado um exemplo. Considere este método:
List matchList(Directory d1, Directory d1,
FilenameFilter excluder);
A descrição do comportamento desse método poderia ser esta:
Retorna uma Lista dos nomes de caminho absolutos de todos os arquivos que aparecem em ambos os Diretórios. Os subdiretórios são descendentes. [...] Os nomes de arquivo que correspondem a excluder são excluídos da lista retornada. A exclusão (excluder) aplica-se somente a diretórios de nível superior e não a nomes de arquivo em subdiretórios.
As palavras "e" e "ou" não aparecem. Mas quando um nome de arquivo é incluído na lista de retorno? Quando ele aparece no primeiro diretório e no segundo diretório e está em um diretório de nível inferior ou não está excluído especificamente. Em código:
if (appearsInFirst && appearsInSecond &&
(inLowerLevel || !excluded)) {
add to list
}
Aqui estão as idéias de teste para essa expressão em formato de tabela:
appearsInFirst |
appearsInSecond |
inLower |
excluded |
verdadeiro |
verdadeiro |
falso |
verdadeiro |
verdadeiro |
verdadeiro |
falso |
falso |
verdadeiro |
verdadeiro |
verdadeiro |
verdadeiro |
verdadeiro |
falso |
falso |
falso |
falso |
verdadeiro |
falso |
falso |
A abordagem geral para descobrir boolean expressions implícitas no texto é primeiro listar as ações descritas (como "retorna um nome correspondente"). Em seguida, escrever uma boolean expression que descreva os casos em que uma ação é adotada e derivar idéias de teste de todas as expressões.
Esse processo dá margem a incompatibilidades. Por exemplo, uma pessoa poderia escrever a boolean expression usada acima. Uma outra poderia dizer que existem efetivamente duas ações distintas: primeiro, o programa descobre nomes correspondentes e, em seguida, os filtra. Assim, em vez de uma expressão, existem duas:
Essas abordagens diferentes podem resultar em idéias de teste diferentes e, portanto, em testes diferentes. Mas as diferenças provavelmente não têm muita importância. Ou seja, o tempo seria empregado de uma forma mais produtiva em outras técnicas e na produção de mais testes do que preocupando-se com a expressão correta e testando alternativas. Se você estiver curioso sobre os tipos de diferenças que poderiam surgir, continue lendo.
A segunda pessoa obteria dois conjuntos de idéias de teste.
idéias de teste sobre a descoberta de uma correspondência:
- o arquivo está no primeiro diretório, o arquivo está no segundo diretório (verdadeiro, verdadeiro)
- o arquivo está no primeiro diretório, o arquivo não está no segundo diretório (verdadeiro, falso)
- o arquivo não está no primeiro diretório, o arquivo está no segundo diretório (falso, verdadeiro)
idéias de teste sobre a filtragem de uma correspondência (uma vez que uma tenha sido descoberta):
- arquivos correspondentes estão no nível superior, o nome corresponde ao excluder (verdadeiro, verdadeiro)
- arquivos correspondentes estão no nível superior, o nome não corresponde ao excluder (verdadeiro, falso)
- arquivos correspondentes estão em algum nível inferior, o nome corresponde ao excluder (falso, verdadeiro)
Suponha que esses dois conjuntos de idéias de teste sejam combinados. As idéias do segundo conjunto só têm importância quando o arquivo está em ambos os diretórios; assim, elas podem ser combinadas apenas com a primeira idéia do primeiro conjunto. O resultado disso seria:
arquivo no primeiro diretório |
arquivo no segundo diretório |
no nível superior |
combina com o excluder |
verdadeiro |
verdadeiro |
verdadeiro |
verdadeiro |
verdadeiro |
verdadeiro |
verdadeiro |
falso |
verdadeiro |
verdadeiro |
falso |
verdadeiro |
Duas das idéias de teste sobre a descoberta de uma correspondência não aparecem nessa tabela. Podemos adicioná-las da seguinte forma:
verdadeiro |
falso |
- |
- |
falso |
verdadeiro |
- |
- |
As células em branco indicam que as colunas são irrelevantes.
Agora, essa tabela ficaria bastante semelhante à tabela da primeira pessoa. A semelhança pode ser enfatizada com o uso da mesma terminologia. A tabela da primeira pessoa tem uma coluna denominada "inLower" e a da segunda pessoa tem uma coluna denominada "in top level". É possível convertê-las invertendo o sentido dos valores. Fazendo isso, obtemos esta versão da segunda tabela:
appearsInFirst |
appearsInSecond |
inLower |
excluded |
verdadeiro |
verdadeiro |
falso |
verdadeiro |
verdadeiro |
verdadeiro |
falso |
falso |
verdadeiro |
verdadeiro |
verdadeiro |
verdadeiro |
verdadeiro |
falso |
- |
- |
falso |
verdadeiro |
- |
- |
As três primeiras linhas são idênticas à tabela da primeira pessoa. As duas últimas diferem somente porque essa versão não especifica valores especificados pela primeira. Isso leva a uma suposição sobre a forma como o código foi escrito. A primeira considerou uma boolean expression complexa:
if (appearsInFirst && appearsInSecond &&
(inLowerLevel || !excluded)) {
add to list
}
A segunda considera boolean expressions aninhadas:
if (appearsInFirst && appearsInSecond) {
// found match.
if (inTopLevel && excluded) {
// filter it
}
}
A diferença entre as duas é que as idéias de teste da primeira detecta dois erros não detectados pelas idéias da segunda porque esses erros não se aplicam.
|
Rational Unified Process
|