Diretriz: Idéias de Teste para Booleans e Fronteiras
As idéias de teste são baseadas em defeitos de software plausíveis e em como esses defeitos podem ser melhor resolvidos. Essa diretriz explica como desenvolver Idéias de Teste para expressões booleanas e condições de limite.
Relacionamentos
Elementos Relacionados
Descrição Principal

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:

A

verdadeiro

falso


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:

  1. finished é igual à required
  2. finished é um pouco menos que essa expressão: required

As regras para expressões booleanas resultariam nestas idéias de teste:

  1. finished < required deve ser verdadeiro
  2. 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.)

  1. count=4, sempre falso
  2. count=5, sempre verdadeiro
  3. 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:

  1. count=4, sempre falso
  2. count=5, sempre verdadeiro
  3. 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.

  1. 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.
  2. 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.)