Introdução
As ideias de teste se baseiam em modelos de falhas, noções de quais falhas são plausíveis no software e como elas podem
ser melhor descobertas. Esta diretriz mostra como criar ideias de teste a partir de expressões booleanas e relacionais.
Primeiro ela motiva as técnicas olhando para o código, depois descreve como aplicá-las se o código ainda não foi
escrito ou não estiver disponível.
Expressões Booleanas
Considere o seguinte trecho de código, extraído de um sistema (imaginário) para gestão de detonação de bomba. Ele é
parte do sistema de segurança e controla se a ativação do botão "detonar bomba" foi obedecida.
if (publicIsClear || technicianClear) { bomb.detonate(); }
O código está errado. O || deve ser um &&. Este erro irá gerar
efeitos ruins. Ao invés de detonar a bomba quando tanto o técnico de bombas quanto o público estiverem a salvo, o
sistema irá detonar quando qualquer um estiver a salvo.
Qual teste deveria encontrar este erro?
Considere um teste em que o botão é acionado quando tanto o técnico quanto o público estiverem a salvo. O código
permitirá que a bomba seja detonada. Porém, e isto é importante, o código correto (o que usa um &&) deverá fazer o mesmo. Então o teste é inútil para encontrar esta falha.
Da mesma forma, este código incorreto se comporta corretamente quando tanto o técnico quanto o público estiverem
próximos a bomba: a bomba não é detonada.
Para encontrar o erro, você deverá ter um caso em que o código escrito avalie de forma diferente do código que deveria
ter sido escrito. Por exemplo, o público deve estar a salvo, mas o técnico ainda estar próximo da bomba. Aqui estão
todos os testes em forma de tabela:
|
publicoAsalvo
|
tecnicoAsalvo
|
O código como escrito...
|
O código correto deveria...
|
|
|
verdadeiro
|
verdadeiro
|
detona
|
detonado
|
o teste é inútil (para esta falha)
|
|
verdadeiro
|
falso
|
detona
|
não detonado
|
teste útil
|
|
falso
|
verdadeiro
|
detona
|
não detonado
|
teste útil
|
|
falso
|
falso
|
não detona
|
não detonado
|
o teste é inútil (para esta falha)
|
Os dois testes ao meio são úteis para encontrar essa falha em particular. Note, entretanto, que eles são redundantes:
uma vez que ambos encontrarão a falha, você não precisa executar ambos.
Existem outras formas nas quais a expressão pode estar errada. Aqui estão duas listas de erros comuns em expressões
booleanas. As falhas à esquerda são descobertas pela técnica aqui discutida. As falhas à direita pode não ser. Então
esta técnica não captura todas as falhas que gostaríamos, mas ainda é muito útil.
|
Falhas detectadas
|
Falhas possivelmente não detectadas
|
|
Usando o operador errado: a || b deveria ser a &&b
|
Variável errada usada: a&&b&&c deveria ser
a&&x&&d
|
|
A negação está omitida ou incorreta: a||b deveria ser !a||b, ou
!a||b deveria ser a||b
|
A expressão é muito simples: a&&b deveria ser a&&b&&c
|
|
Falta parêntesis na expressão: a&&b||c deveria ser a&&(b||c)
|
Expressões com mais de uma das falhas da coluna da esquerda
|
A expressão é excessivamente complexa: a&&b&&c deveria ser a&&b
(Esta falha não é tão provável, mas é fácil de encontrar com testes úteis para outras razões).
|
|
Como estas ideias são usadas? Suponha que você tenha uma expressão booleana tal como a&&!b. Você pode construir uma tabela verdade como esta:
|
a
|
b
|
a&&!b
(o código escrito)
|
talvez devesse ser
a||!b
|
talvez devesse ser
!a&&!b
|
talvez devesse 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 você verificar todas as possibilidades, descobrirá que a primeira a segunda e a quarta são as únicas necessárias. A
terceira expressão não irá encontrar nenhuma falha que não tenha sido encontrada por uma das outras, então você não
precisa experimentá-la. (À medida que as expressões se tornam mais complicadas, é cada vez mais importante evitar os
casos desnecessários).
Naturalmente, nenhuma pessoa sã construiria uma tabela dessas. Felizmente, você também não precisa. É fácil memorizar
os casos necessários para expressões simples. Veja a próxima seção. Para expressões mais complexas, tais como A&&B||C, veja Ideias de Teste Para Misturas de Es e OUs, que enumera ideias de teste para
expressões com dois ou três operadores. Para expressões ainda mais complexas, um programa pode ser usado para gerar ideias 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 && ... && A
n, teste com:
|
A1, A2, ..., An todos verdadeiros
|
|
A1 falso e todo resto verdadeiro
|
|
A2 falso e todo resto verdadeiro
|
|
...
|
|
An falso e todo resto verdadeiro
|
Se a expressão for A1 || A2 || ... || A n, teste com:
|
A1, A2, ..., An todos falsos
|
|
A1 verdadeiro e todo resto falso
|
|
A2 verdadeiro e todo resto falso
|
|
...
|
|
An verdadeiro e todo resto falso
|
Se a expressão for A, teste com:
Então, quando você precisar testar a&&!b, você pode aplicar a primeira tabela acima,
inverter o sentido de b (porque é negado), e obter esta lista de Ideias de
Teste:
-
A verdadeiro, B falso
-
A verdadeiro, B verdadeiro
-
A falso, B falso
Expressões Relacionais
Aqui está outro exemplo de código com falha:
if (terminado < necessário) { sirene.soa(); }
O < deveria ser <=. Esses erros são muito comuns. Assim como as
expressões booleanas, você pode construir uma tabela de valores de teste e ver quais os que detectam a falha:
|
terminado
|
necessário
|
O código como escrito...
|
o código correto deveria ser...
|
|
1
|
5
|
soar a sirene
|
soado a sirene
|
|
5
|
5
|
não soar a sirene
|
soado a sirene
|
|
5
|
1
|
não soar a sirene
|
não soado a sirene
|
Mais genericamente, a falha pode ser detectada sempre que terminado = necessário. Da análise das
falhas plausíveis, podemos obter essas regras para ideias de teste:
Se a expressão for A<B ou A>=B, teste com
|
A=B
|
|
A um pouco menor que B
|
Se a expressão for A>B ou A<=B, teste com
|
A=B
|
|
A um pouco maior que B
|
O que significa "um pouco"? Se A e B forem inteiros, A deve ser uma unidade menor ou maior do que B. Se forem números
de ponto flutuante, A deve ser um número muito próximo de B. (provavelmente não será necessário que ele seja o número
de ponto flutuante mais próximo de B.)
Regras Para Expressões Relacionais e Booleanas
Combinadas
A maioria dos operadores relacionais ocorre com expressões booleanas, como neste exemplo:
if (terminado < necessário) { sirene.soa(); }
As regras para expressões relacionais levariam a estas ideias de teste:
-
terminado é igual a necessário
-
terminado é ligeiramente inferior a necessário
As regras para expressões booleanas levariam a estas:
-
terminado < necessário deveria ser verdadeiro
-
terminado < necessário deveria ser falso
Mas se terminado é um valor ligeiramente inferior a necessário, terminado < necessário é verdade, então não há razão para escrever a última.
E se terminado é igual a necessário, terminado <
necessário é falso, então não há razão para escrever a última também.
Então, se uma expressão relacional não tiver operadores booleanos (&& e ||), ignore o fato dela também ser uma expressão booleana.
As coisas são um pouco mais complicadas com combinações de operadores booleanos e relacionais, como esta:
if (contagem<5 || sempre) (sirene.soa();)
Da expressão relacional, você obtém:
-
contagem ligeiramente inferior a 5
-
contagem igual a 5
Da expressão booleana, você obtém:
-
contagem<5 verdadeiro, sempre falso
-
contagem<5 falso, sempre verdadeiro
-
contagem<5 falso, sempre falso
Estes podem ser combinados em três ideias de teste mais específicas. (note que contagem é um
inteiro.)
-
contagem=4, sempre falso
-
contagem=5, sempre verdadeiro
-
contagem=5, sempre falso
Note que contagem=5 foi usada duas vezes. Pareceria melhor usá-lo apenas uma vez, para permitir
o uso de algum outro valor - afinal, por que testar contagem com 5 duas vezes? Não seria melhor
testá-lo uma vez com 5 e outra vez com algum outro valor de forma que contagem<5 seja falso?
Poderia ser, mas é perigoso tentar. Porque é fácil cometer um erro. Suponha que você tentou o seguinte:
-
contagem=4, sempre falso
-
contagem=5, sempre verdadeiro
-
contagem<5 falso, sempre falso
Suponha que há uma falha que possa somente ser capturada com contagem=5. O que significa
que o valor 5 irá fazer com que contagem<5 resulte em falso no segundo teste, quando o código
correto teria resultado em verdadeiro. Entretanto, este valor falso é imediatamente comparado com o valor de sempre,
que é verdadeiro. Isso significa que o valor de toda a expressão está correto, mesmo se valor da sub-expressão
relacional estiver errado. A falha permanecerá escondida.
A falha não permanecerá escondida se o outro contagem=5 for deixado menos específico.
Problemas semelhantes acontecem quando a expressão relacional está no lado direito do operador booleano.
Como é difícil saber quais sub-expressões têm que ser exatas, e quais podem ser gerais, é melhor torná-las todas
exatas. A alternativa é usar o programa de
expressões booleanas mencionado acima. Ele produz ideias de teste corretas para combinações de expressões booleanas
e relacionais arbitrárias.
Ideias de teste sem Código
Como explicado no Concept: Design Teste-Primeiro, normalmente é preferível projetar os testes antes da implementação do código. Então, apesar
das técnicas serem motivadas por exemplos de código, elas irão normalmente ser aplicadas sem código. Como?
Alguns artefatos de design, tais como gráficos de estado e diagramas de sequência, usam expressões booleanas como
guardas. Esses casos são muito simples, basta adicionar as ideias de teste das expressões booleanas a lista de
verificação de ideias de teste do artefato. Veja Diretriz: Ideias de Teste para Gráficos de Estado e Diagramas de Atividade.
O caso mais interessante é quando as expressões booleanas são implícitas e não explícitas. O que normalmente é o caso
nas descrições das APIs. Aqui está um exemplo. Considere este método:
List matchList(Directory d1, Directory d1, FilenameFilter excluder);
A descrição do comportamento deste método pode ser tal como esta:
Retorna uma Lista de caminhos absolutos de todos os arquivos que aparecem nos dois Diretórios. Os subdiretórios são
descendentes. [...] Os nomes de arquivos que coincidirem com excluder serão excluídos da
lista retornada. O excluder só se aplicará aos diretórios de nível superior, e não aos nomes dos arquivos em
subdiretórios.
As palavras "e" e "ou" não aparecem. Mas quando um arquivo é incluído na lista retornada? Quando ele aparece no
primeiro diretório e no segundo diretório e também está em um diretório de nível inferior ou não
está expressamente excluído. No código:
if (appearsInFirst && appearsInSecond && (inLowerLevel || !excluded)) { add to list }
Aqui estão as ideias de teste para essa expressão, em uma forma tabular:
|
appearsInFirst
|
appearsInSecond
|
inLower
|
excluído
|
|
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 em texto é listar primeiro as ações descritas (tal
como "retorna um nome correspondido"). Então, escrever uma expressão booleana que descreve os casos em que uma ação é
feita. Derive as ideias de teste a partir de todas as expressões.
Existe espaço para divergência nesse processo. Por exemplo, uma pessoa poderia escrever a expressão booleana descrita
acima. Outra poderia dizer que existem realmente duas ações distintas: primeiro, o programa descobre nomes
correspondentes e, os seleciona. Então, ao invés de uma expressão, existem duas:
-
descobrir uma correspondência:
-
Acontece quando um arquivo está no primeiro diretório e um arquivo com o mesmo nome está no segundo
diretório
-
filtrar uma correspondência:
-
Acontece quando os arquivos correspondentes estão no nível mais alto e o nome coincide com excluder
Estas diferentes abordagens podem levar a diferentes ideias de teste e a diferentes testes. Mas as diferenças parecem
não ser particularmente importantes. Ou seja, o tempo despendido com a preocupação sobre qual expressão está correta, e
a tentativa de alternativas, seria melhor empregado em outras técnicas e na produção de mais testes. Se você estiver
curioso sobre quais tipos de diferenças poderiam existir, leia.
A segunda pessoa poderia ter dois conjuntos de ideias de teste.
ideias de teste sobre a descoberta de uma correspondência:
-
arquivo no primeiro diretório, arquivo no segundo diretório (verdadeiro, verdadeiro)
-
arquivo no primeiro diretório, arquivo não está no segundo diretório (verdadeiro, falso)
-
arquivo não está no primeiro diretório, arquivo no segundo diretório (falso, verdadeiro)
ideias de teste sobre a filtragem de uma correspondência (uma foi descoberta):
-
os arquivos correspondentes estão no nível superior, o nome corresponde ao excluder
(verdadeiro, verdadeiro)
-
os arquivos correspondentes estão no nível superior, o nome não corresponde ao excluder
(verdadeiro, falso)
-
os arquivos correspondentes estão em algum nível inferior, o nome corresponde ao excluder (falso, verdadeiro)
Suponha que esses dois conjuntos de ideias de teste estejam combinados. Os que estão no segundo conjunto só são
importantes quando o arquivo está em ambos os diretórios, de modo que só possam ser combinados com a primeira ideia no
primeiro conjunto. Isto nos dá o seguinte:
|
arquivo no primeiro diretório
|
arquivo no segundo diretório
|
no nível superior
|
Coincide com o excluder
|
|
verdadeiro
|
verdadeiro
|
verdadeiro
|
verdadeiro
|
|
verdadeiro
|
verdadeiro
|
verdadeiro
|
falso
|
|
verdadeiro
|
verdadeiro
|
falso
|
verdadeiro
|
Duas das ideias de teste sobre a descoberta de uma correspondência não aparecem na tabela. Nós podemos adicioná-las
assim:
|
arquivo no primeiro diretório
|
arquivo no segundo diretório
|
no nível superior
|
Coincide 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, esta tabela está bastante semelhante a tabela da primeira pessoa. A semelhança pode ser enfatizada usando a
mesma terminologia. A tabela da primeira pessoa tem uma coluna chamada "inLower", e a da segunda pessoa tem uma chamada
"no nível superior". Elas podem ser convertidas trocando o sentido dos valores. Ao fazer isso, nós temos esta segunda
versão da tabela:
|
appearsInFirst
|
appearsInSecond
|
inLower
|
excluído
|
|
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 as da tabela da primeira pessoa. As duas últimas diferem apenas porque esta
versão não especifica os valores que a primeira especificou. Trata-se de uma hipótese sobre como o código foi escrito.
A primeira assumiu uma expressão booleana complicada:
if (appearsInFirst && appearsInSecond && (inLowerLevel || !excluded)) { add to list }
a segunda pressupõe expressões booleanas aninhadas:
if (appearsInFirst && appearsInSecond) { // found match. if (inTopLevel && excluded) { // filter it } }
A diferença entre as duas é que as ideias de teste para a primeira detectam duas falhas que as ideias para a segunda
não, porque essas falhas não se aplicam.
-
Na primeira implementação, pode haver uma falta de parêntesis. Os parênteses ao redor de ||
estão corretos ou incorretos? Visto que a segunda implementação não tem || nem parênteses, a
falha não pode existir.
-
Os requisitos de teste para a primeira implementação verificam se o segundo && deve
ser um ||. Na segunda implementação, onde o && explícito é
substituído pelo && implícito das declarações if aninhadas.
Não existe nenhuma falha ||-para-&&, por si só. (pode ser o
caso da nidificação estar incorreta, mas esta técnica não trata disso.)
|