Introdução
As idéias de teste baseiam-se em modelos de falha, noções de quais falhas são plausíveis no software e como essas
falhas podem ser melhor descobertas. Esta diretriz mostra como criar idéias de teste a partir de expressões booleanas e
expressões relacionais. Primeiro, ela motiva as técnicas através de um exame do código e, depois, descreve como
aplicá-las se o código ainda não tiver sido escrito ou não estiver disponível.
Expressões Booleanas
Considere o seguinte fragmento de código, extraído de um sistema (imaginário) de gerenciamento de detonação de bombas.
Ele faz parte do sistema de segurança e controla se o pressionamento do botão "Detonar Bomba" é obedecido.
if (publicIsClear || technicianClear) {
bomb.detonate();
}
O código está incorreto. || deve ser um &&. Esse erro terá
efeitos graves. Em vez de detonar a bomba quando o técnico e o público tiverem se afastado, o sistema detonará quando
um deles tiver se afastado.
Que teste localizaria esse erro?
Considere um teste em que o botão é pressionado quando o técnico e o público tiverem se afastado. O código permitirá a
detonação da bomba. Mas - e isso é importante - o código correto (aquele que utiliza um &&) deve fazer o mesmo. Assim, o teste não consegue localizar esse erro.
De modo semelhante, esse código incorreto se comporta corretamente quando o técnico e o público estão próximos à bomba:
a bomba não é detonada.
Para localizar o erro, é preciso ter um caso em que o código escrito seja avaliado de forma diferente do código que
deveria ter sido escrito. Por exemplo, o público deve ter se afastado, mas o técnico ainda está próximo à bomba. Aqui
estão todos os testes em formato de tabela:
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. Observe, no entanto, que eles são
redundantes: como um deles localizará a falha, não é necessário executar ambos.
A expressão poderia apresentar outros erros. Abaixo são apresentadas duas listas de erros comuns em expressões
booleanas. 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.
Falhas detectadas
|
Falhas possivelmente não detectadas
|
Utilizando o operador incorreto: a || b deve ser a&& b
|
Variável incorreta utilizada: a&&b&&c deve ser
a&& x&&d
|
Negação omitida ou incorreta: a||b deve ser !a||b, ou ! a||b deveria ser a||b
|
Expressão bastante simples: a&&b deve ser a&&b&&c
|
A expressão entre parênteses está configurada incorretamente: a&&b||c deve ser a&&(b||c)
|
Expressões com mais de um dos erros na coluna da esquerda
|
Expressão muito complexa: a&&b&&c deve ser a&&b
(Esta falha não é muito provável, mas é fácil de ser localizada com testes úteis por outras
razões.)
|
|
Como essas idéias são usadas? Suponha que esteja oferecendo uma expressão booleana como a&&!b. Você poderia construir uma tabela verdadeira como esta:
a
|
b
|
a&&!b
(código conforme gravado)
|
possivelmente deveria ser
a||!b
|
possivelmente deveria ser
! a&&!b
|
possivelmente deveria ser
a&&b
|
...
|
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 obter 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 obter expressões ainda mais complexas, pode-se utilizar um programa para gerar idéias de teste.
Tabelas para Expressões Booleanas Simples
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:
Então, quando precisar testar a&&!b, poderá aplicar a primeira tabela acima, inverter o
sentido de b (porque é negado), e obter essa lista de Idéias de
Teste:
-
A verdadeiro, B falso
-
A verdadeiro, B verdadeiro
-
A falso, B falso
Expressões Relacionais
Um outro exemplo de código com erro é:
if (finished < required) { siren.sound(); }
< deve ser um <=. Esses erros são bastante comuns. Da mesma forma
que com expressões booleanas, 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
|
De modo mais geral, a falha pode ser detectada sempre 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 o seguinte:
A=B
|
A ligeiramente menor que B
|
Se a expressão for A>B ou A<=B, teste com o seguinte:
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.)
Regras para Expressões Booleanas e Relacionais
Combinadas
A maioria dos operadores relacionais ocorre em expressões booleanas, como neste exemplo:
if (finished < required) { siren.sound(); }
As regras para expressões relacionais resultariam nestas idéias de teste:
-
finished é igual à required
-
finished é um pouco menos que essa expressão: required
As regras para expressões booleanas resultariam nestas idéias de teste:
-
finished < required deve ser verdadeiro
-
finished < required deve ser falso
Ele pode ser essa expressão: finished é um pouco menos que essa expressão: required, finished < required é verdadeiro, então, não há nenhum motivo para
anotação posterior.
Se essa expressão for falsa, não há motivos para anotá-la: finished igual a required , finished < required .
Se uma expressão relacional não contiver nenhum operador booleano (&& e ||), ignore o fato que ele também é uma expressão booleana.
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:
-
count um pouco menos que 5
-
count igual a 5
A partir da expressão booleana, observa-se que:
-
count<5 verdadeiro, sempre falso
-
count<5 falso, sempre verdadeiro
-
count<5 falso, sempre falso
Essas expressões podem ser combinadas em três idéias de teste mais específicas. (Aqui, note que count é um inteiro.)
-
count=4, sempre falso
-
count=5, sempre verdadeiro
-
count=5, sempre falso
Note que count=5 é utilizado duas vezes. Talvez seja melhor utilizá-lo apenas uma vez, para
permitir o uso de algum outro valor; afinal, por que testar essa expressão com 5 vezes: count ?
Não seria melhor tentá-lo uma vez com 5 e uma outra vez com algum outro valor de modo que o resultado seja falso?
(Exemplo: count<5 Seria, mas é perigoso tentar porque é fácil cometer um erro. Suponha que
você tentasse o seguinte:
-
count=4, sempre falso
-
count=5, sempre verdadeiro
-
count<5 falso, sempre falso
Suponha que haja uma falha que pode ser capturada apenas com o seguinte valor: count=5. Isso
significa que o valor 5 produzirá "false" na expressão count<5, quando o código correto devia
produzir true. Entretanto, imediatamente é feito um OR desse valor false com o valor always, que é true. 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.
A falha 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 é utilizar o programa de expressão
booleana mencionado acima. Ele produz idéias de teste corretas para uma combinação arbitrária de expressões
booleanas e expressões relacionais.
Idéias de Teste sem Código
Como explicado em Conceito: 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 expressões booleanas como
guardas. Esses casos são diretos, basta incluir as idéias de teste das expressões booleanas na lista de verificação de
idéias de teste do artefato. Consulte Diretriz do Produto de Trabalho: Idéias de Teste para Diagramas de Estado e de
Atividade.
O caso mais complicado é quando as expressões booleanas são implícitas em vez de explícitas. Geralmente, isso ocorre em
descrições de APIs. Eis 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. [...] Nomes que 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.
A palavras "and" e "or" não aparecem. Mas quando um nome de arquivo é incluído na lista de retorno? Quando ele aparece
no primeiro diretório e aparece no segundo diretório e está em um diretório de nível mais baixo ou
não é excluído de forma específica. 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 expressões booleanas implícitas do texto é primeiro listar as ações descritas (como
"retorna um nome correspondente"). Em seguida, escrever uma expressão booleana 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 expressão booleana usada
acima. Uma outra poderia dizer que existem na realidade duas ações distintas: primeiro, o programa descobre nomes
correspondentes, em seguida, os filtra. Assim, em vez de uma expressão, existem duas:
-
descobrir uma correspondência:
-
ocorre quando um arquivo está no primeiro diretório e um arquivo com o mesmo nome está no segundo diretório
-
filtrar uma correspondência:
-
ocorre quando os arquivos correspondentes estão no nível superior e o nome corresponde ao excluder
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 a excluder
(vedadeiro, verdadeiro)
-
arquivos correspondentes estão no nível superior, o nome não corresponde a excluder
(verdadeiro, falso)
-
arquivos correspondentes estão no nível inferior, o nome corresponde a 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
|
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:
Arquivo no primeiro diretório
|
Arquivo no segundo diretório
|
Nível superior
|
Combina com o excluder
|
verdadeiro
|
verdadeiro
|
verdadeiro
|
verdadeiro
|
verdadeiro
|
verdadeiro
|
verdadeiro
|
falso
|
verdadeiro
|
verdadeiro
|
falso
|
verdadeiro
|
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
denominada "no nível superior". É 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 expressão booleana complexa:
if (appearsInFirst && appearsInSecond && (inLowerLevel || !excluded)) { add to list }
A segunda considera expressões booleanas 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.
-
Na primeira implementação, pode haver uma falha nos parênteses. Os parênteses ao redor de ||
estão corretos ou incorretos? Como a segunda implementação não tem parênteses e nenhum ||, a
falha não pode existir.
-
Os requisitos de teste para a verificação da primeira implementação para ser a segunda expressão, &&, deve ser um ||. Na segunda implementação, que o explícito && é substituída pelo implícito &&. Não há ||-for-&& falha, por se. (Talvez o aninhamento esteja incorreto,
mas essa técnica não considera isso.)
|