Diretriz: Ideias de Teste Para Valores Limítrofes e Booleanos
Relacionamentos
Elementos Relacionados
Descrição Principal

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:

A

verdadeiro

falso


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:

  1. terminado é igual a necessário
  2. terminado é ligeiramente inferior a necessário

As regras para expressões booleanas levariam a estas:

  1. terminado < necessário deveria ser verdadeiro
  2. 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.)

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

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

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